fix(rectification): stop stamping pending receipts and document aggregate blocker
Implement BUG-984 F2 option A and reproduce mixed completed identities through real tools. Stop at the required SQL authorization boundary; F1/F3/F4 remain pending. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
# BLOCKED
|
# BLOCKED
|
||||||
|
|
||||||
## 跨午夜前置修复合入:Gitea 写入认证失败(2026-09-20)
|
## BUG-984 补单:F2 混合成功身份实证触发 SQL 授权停点(2026-09-20)
|
||||||
|
|
||||||
|
- A 已局部实现:started/failed 不写算法身份,completed 只用实际结果来源。真实工具 + 原生 golden 的同 turn compare/diagnostics 组合可产生 scoring-9、scoring-10 两条成功回执;字符串 max 返回 scoring-9,不是最近成功 scoring-10。该测试证明缺陷仍存在,不是聚合修复通过。
|
||||||
|
- 既有 RPC 输出没有各 completed 的来源身份;历史 fingerprint 也不能还原。按补单须升 B(兼容函数体迁移),但当前执行授权禁止自行迁移,已停止进一步业务实施并报告。F1/F3/F4 pending;未改 SQL/UI/缓存、未推送。见 `docs/tasks/PROGRESS-rectification-cross-midnight-fix-20260920.md`。
|
||||||
|
|
||||||
|
## ~~跨午夜前置修复合入:Gitea 写入认证失败(2026-09-20)~~
|
||||||
|
|
||||||
|
- **已解除**:主会话成功将前置代码 `3f39bafc4a1fb7fc9d0a257d2528ea1a64295792` 推 staging 并 ls-remote 核对;本轮 fetch 亦看到其后纯文档 `f09f3d80`。下面保留原认证失败历史,不再作为 BUG-984 开工阻塞;部署另行验收。
|
||||||
|
|
||||||
- 用户授权将已 review 通过的 `25232ce4` 合入 staging,并执行 BUG-984。已同步 `a3577ce2`,本地整合提交 `b27d4de963f9ba7c4072233cabe06c6875eb0582`;缓存补单完整保留 staging 版,BUG-985 按第三环境复验改 resolved。
|
- 用户授权将已 review 通过的 `25232ce4` 合入 staging,并执行 BUG-984。已同步 `a3577ce2`,本地整合提交 `b27d4de963f9ba7c4072233cabe06c6875eb0582`;缓存补单完整保留 staging 版,BUG-985 按第三环境复验改 resolved。
|
||||||
- `git push origin HEAD:staging` 被拒:`remote: Failed to authenticate user` / `fatal: Authentication failed`。随后 `git ls-remote` 确认远端仍 `a3577ce2fa0bfa6047d397e50ed2e676c58a4420`,不得声称已交付或已部署。
|
- `git push origin HEAD:staging` 被拒:`remote: Failed to authenticate user` / `fatal: Authentication failed`。随后 `git ls-remote` 确认远端仍 `a3577ce2fa0bfa6047d397e50ed2e676c58a4420`,不得声称已交付或已部署。
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 印度占星 Skill 更新日志
|
# 印度占星 Skill 更新日志
|
||||||
|
|
||||||
|
## 2026-09-20 — 校正开始与失败回执不再冒充结果来源(局部实现,未发布)
|
||||||
|
|
||||||
|
compare/diagnostics 开始或失败时不写算法身份,成功时只记录实际结果来源,不回退部署默认值。同轮多个成功结果仍可能被旧 SQL 字符串聚合选错,本轮因此停在迁移授权边界;缓存只读策略尚未实施,不宣称 BUG-984 已修。Skill 版本不变。
|
||||||
|
|
||||||
## 2026-09-20 — 修正跨午夜出生范围的候选打分
|
## 2026-09-20 — 修正跨午夜出生范围的候选打分
|
||||||
|
|
||||||
跨午夜窗口中的候选改用各自日期计算 Dasha 边界,避免午夜后的候选沿用前一天日期;候选分数与排序可能随之变化,打分权重和确认门不变。后端算法身份与 V9 默认回执版本同步更新;版本接口可达且未被环境覆盖的分钟评分会按既有规则失效旧缓存。时段缓存和聚合回执仍有既有追溯缺口,未宣称所有旧结果都已重新计算。历史回执不重写,Skill 版本不变。部署与受控会话验收状态见 `docs/tasks/PROGRESS-rectification-cross-midnight-20260920.md`。
|
跨午夜窗口中的候选改用各自日期计算 Dasha 边界,避免午夜后的候选沿用前一天日期;候选分数与排序可能随之变化,打分权重和确认门不变。后端算法身份与 V9 默认回执版本同步更新;版本接口可达且未被环境覆盖的分钟评分会按既有规则失效旧缓存。时段缓存和聚合回执仍有既有追溯缺口,未宣称所有旧结果都已重新计算。历史回执不重写,Skill 版本不变。部署与受控会话验收状态见 `docs/tasks/PROGRESS-rectification-cross-midnight-20260920.md`。
|
||||||
|
|||||||
+3
-2
@@ -13009,12 +13009,13 @@
|
|||||||
- 触发条件:历史时段缓存证据指纹未变;本次运行默认版本已升,而缓存算法身份仍旧。
|
- 触发条件:历史时段缓存证据指纹未变;本次运行默认版本已升,而缓存算法身份仍旧。
|
||||||
- 根因:`scoreAndPersistCurrentEvidence()` 的时段分支在版本探测之前按证据指纹提前返回;成功回执使用旧结果身份,但 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`。
|
- 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` 待批准后执行。
|
- 修复:补单策略 b 已批准,前轮认证 blocker 已由主会话解除(前置代码 `3f39bafc` 已合 staging,部署另验)。本轮仅完成 F2 A:compare/diagnostics 的 started/failed 不再写版本,completed 不回退前端常量。真实工具同 turn 混版本 completed 已实测成立,触发 B,但 SQL 未授权,停止进一步实施;F1/F3/F4 pending,BUG 保持未解决。进度见 `docs/tasks/PROGRESS-rectification-cross-midnight-fix-20260920.md`。
|
||||||
- 验证:独立纯虚构 TS 探针使用替身 RPC/fetch,旧时段缓存返回 `cached:true`、算法尾号 -7、fetch 次数 0;真实工具调用路径记录 started 尾号 -8 / completed 尾号 -7。聚合 SQL 的 `max(engine_version)` 已核源码,未真跑 DB,因此不伪称数据库端到端通过。
|
- 验证:独立纯虚构 TS 探针使用替身 RPC/fetch,旧时段缓存返回 `cached:true`、算法尾号 -7、fetch 次数 0;真实工具调用路径记录 started 尾号 -8 / completed 尾号 -7。聚合 SQL 的 `max(engine_version)` 已核源码,未真跑 DB,因此不伪称数据库端到端通过。
|
||||||
- 防复发:后续必须分别覆盖分钟/时段缓存、实际版本接口、部分环境覆盖与接口失败,成功回执必须绑定实际结果来源;历史打开与 Skill 绑定不变,不得重标或删除旧结果来掩盖问题。
|
- 防复发:后续必须分别覆盖分钟/时段缓存、实际版本接口、部分环境覆盖与接口失败,成功回执必须绑定实际结果来源;历史打开与 Skill 绑定不变,不得重标或删除旧结果来掩盖问题。
|
||||||
- 相关记录:BUG-427、BUG-621、BUG-981。
|
- 相关记录:BUG-427、BUG-621、BUG-981。
|
||||||
- 复发自:未发现同症状既有记录;既有分钟算法身份缓存门不覆盖提前返回的时段分支,回执测试未组合新版 started 与旧缓存 completed。
|
- 复发自:未发现同症状既有记录;既有分钟算法身份缓存门不覆盖提前返回的时段分支,回执测试未组合新版 started 与旧缓存 completed。
|
||||||
- 修复版本:未实施;本轮 T4 仅完成默认及后端身份同步,整体验收未通过。
|
- F2 补充实测:原生虚构输入 golden 经真实 compare/diagnostics 工具顺序执行,仅替换算法标签模拟滚动版本;同一 turn 可写 completed scoring-9 与 scoring-10,而字符串 max 为 scoring-9。既有聚合 RPC 不暴露各成功行身份,历史 fingerprint 无法还原,应用层 A 不足以闭环。定向身份 9/9(含缺陷诊断)、tsc 0;五文件回归 61 项中 57 pass / 4 Windows symlink EPERM,未冒称 DB 聚合已验。
|
||||||
|
- 修复版本:`codex/rectification-cross-midnight-fix-20260920` 的 F2 A 局部实现,B 授权阻塞、未推送;缓存与聚合整体验收未通过。
|
||||||
|
|
||||||
## BUG-985 | 同日不变性回归写死浮点分数哈希导致跨机门禁失败
|
## BUG-985 | 同日不变性回归写死浮点分数哈希导致跨机门禁失败
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
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.
|
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-984 补单前置解除与聚合授权停点
|
||||||
|
|
||||||
|
- 前轮写认证 blocker 已由主会话解除:前置代码 `3f39bafc` 已推 staging 并核对;本执行树 fetch 到后续纯文档 `f09f3d80`。下面旧记录保留为历史,不再作为当前开工 blocker。
|
||||||
|
- A(started/failed 不写版本)不能证明整个回执聚合已修。真实工具同 turn compare/diagnostics 可各写不同身份的 completed,scoring-9/scoring-10 的字符串 max 仍选 9。必须按补单报告 B 授权停点,不能用新 fingerprint 绕过旧历史来源缺失,也不能自动开 SQL 迁移。
|
||||||
|
- 本轮预检 remote verified,focused 23 pass / 1 历史镜像路径断言失败。Docker 29.8.0 可用;Windows 定向注册表实际有 4 项 symlink EPERM,不写成无 Docker。
|
||||||
|
|
||||||
## 2026-09-20 · 跨午夜已验收分支合入写认证失败
|
## 2026-09-20 · 跨午夜已验收分支合入写认证失败
|
||||||
|
|
||||||
- fetch 与 ls-remote 正常不代表写认证有效。获准合入后的 `git push origin HEAD:staging` 报 `Failed to authenticate user`;远端仍 `a3577ce2`,本地已合并为 `b27d4de9`。未重试借用凭据或改认证配置。
|
- fetch 与 ls-remote 正常不代表写认证有效。获准合入后的 `git push origin HEAD:staging` 报 `Failed to authenticate user`;远端仍 `a3577ce2`,本地已合并为 `b27d4de9`。未重试借用凭据或改认证配置。
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# BUG-984 缓存与成功结果身份补单进度(2026-09-20)
|
||||||
|
|
||||||
|
## 结论与授权边界
|
||||||
|
|
||||||
|
**blocked:F2 方案 A 已完成局部实现,实测证实同一 turn 可出现不同身份的多个 completed,按补单须升级 B;未获 SQL 迁移授权,依指令停止进一步业务实施。F1 / F3 / F4 未完成,不可交付为 BUG-984 修复。**
|
||||||
|
|
||||||
|
- 执行树:`.worktrees/rectification-cross-midnight-fix-20260920`,分支 `codex/rectification-cross-midnight-fix-20260920`。
|
||||||
|
- 前置代码基线:`3f39bafc4a1fb7fc9d0a257d2528ea1a64295792`。主会话已成功推 staging 并核对 ls-remote,**前轮 Gitea 写认证 blocker 已解除**;保留旧失败历史,不把它当本轮阻塞。
|
||||||
|
- 本轮 fetch 发现补充文档 `f09f3d809a538a48979b94f1c851d1dc528e49ba`,干净树快进到该提交。它仅修改任务书与索引,代码仍为上述基线。
|
||||||
|
- 已读 AGENTS、前端三规范、BUG-621/981/984 完整记录与错误台账。最大 Bug 编号 985,本轮续写 BUG-984,不占新号。
|
||||||
|
- 不 push、不调用线上、不读取凭据、不改 workflow/main/DNS/SQL/Skill/input contract/评分常数/确认门,不删除或重标历史缓存。
|
||||||
|
|
||||||
|
## F2 实测与应用层可行性
|
||||||
|
|
||||||
|
### A 的局部实现
|
||||||
|
|
||||||
|
`createRectificationV9Tools()` 的 compare / diagnostics started 与 failReceipt 不再携带部署声称版本;completed 只记录实际返回的 algorithmVersion,compare 缺失时不回退前端默认版本。
|
||||||
|
|
||||||
|
同一真实 golden 用例先红后绿:原实现 started 仍写 -8,修改断言后 6 pass / 1 fail;局部修复并补两条测试后 9 pass / 0 fail。
|
||||||
|
|
||||||
|
### 已构造出的 B 触发条件
|
||||||
|
|
||||||
|
`frontend/tests/rectification-engine-version-cross-midnight.test.ts` 的 `runGoldenToolSequence()` 通过实际 `createRectificationV9Tools()` 构造一个 Case/turn,依次运行 compare、diagnostics;RPC/fetch 为受控替身,响应结构和数值来自既有原生引擎虚构输入 golden,仅算法身份标签被替换以模拟滚动版本。未声称执行未来 -9/-10 算法。
|
||||||
|
|
||||||
|
| 行 | 实际工具 | 状态 | 写入身份 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | compare | started | null |
|
||||||
|
| 2 | compare | completed | scoring-9 |
|
||||||
|
| 3 | diagnostics | started | null |
|
||||||
|
| 4 | diagnostics | completed | scoring-10 |
|
||||||
|
|
||||||
|
两条 completed 的 turn_id 相同;最新成功来自 scoring-10,但字符串最大值是 scoring-9。测试保留的是**缺陷诊断证据**,不是“聚合已修”的验收绿灯。并未运行真实 PostgreSQL 聚合,因此不宣称数据库端到端已验证。
|
||||||
|
|
||||||
|
- compare 与 diagnostics 各自独立调用引擎;两次完成之间没有“同 turn 必须同算法”的锁或一致性检查。部署滚动/环境来源差异不会被 A 消除。
|
||||||
|
- 现有 `get_agentic_rectification_turn_receipt()` SQL 按 selected successful attempt 过滤后仍执行 `max(tr.engine_version)`,活动只返回 tool/status/methods/timing/fingerprint,不含各 completed 的 engine_version。
|
||||||
|
- `loadV9TurnReceipt()` 只能读这个聚合字段;既有历史 compare fingerprint 仅含 hash/timings,diagnostics fingerprint 仅为 hash。因此应用层拿既有 RPC 输出无法无损恢复各历史成功行身份,也不能把当前 dossier 最新结果冒充历史 turn 来源。
|
||||||
|
- 在新 fingerprint 塞身份只能处理新数据,修不了历史 max 污染;引入第二条服务角色原始表读取会另造回执权限/attempt/重试排序投影,不是本单 A,且任务书已明确“构造成立就升 B”。因此不绕过授权另造旁路。
|
||||||
|
- 建议下一步 B:新增向后兼容函数体迁移,保留 owner/turn/attempt 过滤,仅从实际成功行取身份;明确多工具/失败重试语义,使用 started_at 与 id 稳定排序,不依赖字符串版本序。不得原地改已应用迁移。主会话审批后再实施并真跑 test:db。
|
||||||
|
|
||||||
|
## 任务状态
|
||||||
|
|
||||||
|
| 项 | 状态 | 剩余工作 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| F1 minute / block_scan 缓存 | pending | 真实 golden 时段旧缓存红测、统一可信身份复用条件;当前完全未改 |
|
||||||
|
| F2 成功回执 | blocked | A 局部完成;多 completed 实证触发 B,等待迁移授权 |
|
||||||
|
| F3 策略 b | pending | 只读标注及所有服务端写入口拒绝尚未实现,不是只藏按钮 |
|
||||||
|
| F4 完整回归/交付 | pending | 全量、build Static/gzip、DB 与受控 staging minute/late-night 验收未完成 |
|
||||||
|
|
||||||
|
## 已执行检查
|
||||||
|
|
||||||
|
| 检查 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| pre_work_check(Python 3.11.7) | remote verified;focused 23 pass / 1 fail,历史 `.workbuddy` 镜像路径断言;与台账已知症状一致,未造目录 |
|
||||||
|
| 工具能力 | Node 22.23.2;Docker daemon 29.8.0 可用;node:22-bookworm / postgres:17-alpine 已有 |
|
||||||
|
| 算法身份文件基线 | 7 / 7 pass |
|
||||||
|
| A 红测 | 6 pass / 1 fail,started 身份断言 |
|
||||||
|
| A + 多成功来源诊断 | 9 / 9 pass;含缺陷存在证明,不是 F2 总体验收通过 |
|
||||||
|
| tsc --noEmit | 0 error |
|
||||||
|
| 定向五文件(含 BUG-621、case-service、skill-registry、activity) | 61 tests / 57 pass / 4 fail;四项均 Windows symlink EPERM,详见下列名单;尚未另跑基线逐条对照,不标全绿 |
|
||||||
|
| lint | 0 error / 120 warnings;未顺手修改既有 warnings |
|
||||||
|
| 全量 / build / Static / gzip / 标准 DB | 因必须 B 的授权停点而暂缓,未执行;不是预设工具缺失 |
|
||||||
|
|
||||||
|
定向四项失败来自未改动的 `skill-registry.test.ts`,单文件复跑 16 tests / 12 pass / 4 fail,失败标题精确如下:
|
||||||
|
|
||||||
|
1. `checked-in registry verifies hashed product packages and leaves consult on the live skill`
|
||||||
|
2. `path traversal and symlink escape fail closed`
|
||||||
|
3. `symbolic links are rejected even when their target stays inside the project root`
|
||||||
|
4. `live consult skill reads a hand-updated tree without a registry hash`
|
||||||
|
|
||||||
|
没有以修改业务代码或弱化安全断言消红。DESIGN 未修改:本轮在 F2 授权停点终止,尚未实施任何 UI 标注/形状变化;后续 F3 必须同提交更新 DESIGN。
|
||||||
|
|
||||||
|
## 断言调整说明
|
||||||
|
|
||||||
|
| 原值 | 新值 | 原因 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 新 Case compare/diagnostics started 回执身份为 CURRENT | started 为 null,completed 保持真实 CURRENT | F2 A 的明确授权;开始阶段未产生结果,不能冒充成功来源 |
|
||||||
|
|
||||||
|
新增两条:diagnostics 503 的 failed 身份为空;同 turn 两工具 completed -9/-10 与字符串最大值相冲突。未知身份的既有缓存降级测试仍如实锁现状,F3 未开始,不声称已修。
|
||||||
|
|
||||||
|
## 部署与后续独立验收
|
||||||
|
|
||||||
|
本 agent 无线上访问。主会话通知前置 run2819 validate 已 success、publish 尚 in_progress,最近通知的 staging health 仍为旧 SHA;这不构成本轮部署或身份验收证据。
|
||||||
|
|
||||||
|
独立验收重点:工具 started/failed 实参、完成态来源无默认回退、混版本诊断测试的边界、旧回执按绑定 Skill 打开不变。F1/F3 的实现与完整测试须在解除 B 授权阻塞后继续。真人清单:`docs/testing/rectification-cross-midnight-fix-20260920.md`。
|
||||||
@@ -293,7 +293,7 @@
|
|||||||
| `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-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 缓存身份来自后端,旧「只写不比」假设作废),**不得动 `skill_version`**(BUG-621:open RPC 要求绑定 Skill 等于当前版本,bump 会让历史校正打不开)。硬红线:只改「按候选日期取 dasha 起始」,不得动 kernel/cap/share 任一常数;确认门不变。BUG-981 | **核心修复与 BUG-985 已 review 通过,本地合并 b27d4de9;推 staging 认证失败,端到端仍受 BUG-984 阻塞** | 实现 `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-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 缓存身份来自后端,旧「只写不比」假设作废),**不得动 `skill_version`**(BUG-621:open RPC 要求绑定 Skill 等于当前版本,bump 会让历史校正打不开)。硬红线:只改「按候选日期取 dasha 起始」,不得动 kernel/cap/share 任一常数;确认门不变。BUG-981 | **核心修复与 BUG-985 已 review 通过,本地合并 b27d4de9;推 staging 认证失败,端到端仍受 BUG-984 阻塞** | 实现 `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` | `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-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` | — | **BUG-984 缓存与结果身份补单(BUG-981 的端到端阻塞项)**:`scoreAndPersistCurrentEvidence()` 的 `block_scan` 分支只比 `evidenceLedgerFingerprint` 即返回 `cached:true` 与旧 `algorithmVersion`,该返回发生在 `readV9EngineScoringIdentity()` **之前**;`minute` 分支则有身份门。F3 已查证完整调用链到 `merge_transition_proximity()`,故核心修复合入后,**证据未变的历史跨午夜时段缓存命中仍返回修复前分数**——在本单闭环前不得声称跨午夜问题已修。边界已按源码收窄:`late_night`(23:00–03:59) 跨日;`unknown`(00:00–23: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 版)。A=started/failed 不再写版本(应用层,必做);A 的漏洞(同 turn 多个 completed 行版本不同)**必须实测取证,不得以「应该不会」结案**;B=聚合改取成功结果那一行(只改函数体,向后兼容);C=拆列本单不做。**四项全部可开工。**硬红线:不重标/不删历史结果,不 bump Skill,不改 V4 input contract,不引入按 `engineVersion` 拒绝打开历史会话。串行:BUG-985 合入 → 本单。BUG-984 | **待领取**(四项全部可开工) | — |
|
| `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:00–03:59) 跨日;`unknown`(00:00–23: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 版)。A=started/failed 不再写版本(应用层,必做);A 的漏洞(同 turn 多个 completed 行版本不同)**必须实测取证,不得以「应该不会」结案**;B=聚合改取成功结果那一行(只改函数体,向后兼容);C=拆列本单不做。**四项全部可开工。**硬红线:不重标/不删历史结果,不 bump Skill,不改 V4 input contract,不引入按 `engineVersion` 拒绝打开历史会话。串行:BUG-985 合入 → 本单。BUG-984 | **blocked:F2 A 局部完成,多 completed 混版本实测成立,等待 B 的 SQL 授权;F1/F3/F4 pending** | `codex/rectification-cross-midnight-fix-20260920`;前置认证已解除,代码基线 `3f39bafc`、文档基线 `f09f3d80`;本轮未推送 |
|
||||||
|
|
||||||
## 命名与归档
|
## 命名与归档
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# BUG-984 补单验收清单(2026-09-20)
|
||||||
|
|
||||||
|
当前 **blocked**:F2 A 局部实现,多 completed 混版本实测触发 B,SQL 未获授权;F1/F3 尚未实现。以下不是已通过清单,不能将本分支当完整修复部署。
|
||||||
|
|
||||||
|
## 自动化续验
|
||||||
|
|
||||||
|
- [ ] 获准 B 后新增兼容函数体迁移,不修改旧迁移,标准 `npm run test:db` 实跑。
|
||||||
|
- [ ] 同一成功 attempt 的 started -8 / completed -7 只展示 -7;历史行字节不变。
|
||||||
|
- [ ] 同 turn compare / diagnostics 多 completed -9 / -10 不按字符串序选 9;明确取最新成功的稳定排序与各工具来源语义。
|
||||||
|
- [ ] 失败后重试、成功后失败、不同 attempt、历史未带新 metadata 都覆盖,不能用当前 dossier 身份替代历史来源。
|
||||||
|
- [ ] minute / late-night block_scan 均用真实 golden 测同身份命中、旧身份重算;未知当前身份时旧缓存只读、不强制重算。
|
||||||
|
- [ ] 无覆盖、完整覆盖、部分覆盖、版本接口超时/错误均区分当前身份与来源。
|
||||||
|
- [ ] 不经界面直接调所有采用/确认/候选选择写入口,旧只读结果均由服务端拒绝。
|
||||||
|
- [ ] `BUG-621` 历史按绑定 Skill 打开回归;不增加 engineVersion 打开相等门。
|
||||||
|
- [ ] tsc、lint、定向、全量、build `/` Static、首屏 gzip ±2%;实际平台失败与基线逐项对照,不削弱断言。
|
||||||
|
|
||||||
|
## 受控 staging 真人验收(由主会话安排)
|
||||||
|
|
||||||
|
只用明确虚构资料与本人受控账号,不读取或借用他人登录态;不得将出生资料、会话内容、标识符或 Cookie 写入记录。
|
||||||
|
|
||||||
|
- [ ] 先核对部署 gitCommit(允许其后纯文档差异),不是只看 HTTP 200。
|
||||||
|
- [ ] 依次测试分钟历史结果与 late-night 时段历史结果;记录是否缓存、实际来源版本、是否重算,不记个人资料。
|
||||||
|
- [ ] 版本探测异常时旧结果仍可查看,显著出现“按旧算法产出”;没有采用/确认/选择写入口,伪造请求也拒绝。
|
||||||
|
- [ ] 已生成历史回执来源保留,不显示 started 的部署版本;旧 Skill 会话仍可从侧栏打开。
|
||||||
|
- [ ] 恢复版本接口后当前同身份缓存可正常复用,身份变化按新规则重算,不删除或重标旧结果。
|
||||||
|
- [ ] 失败重试后活动与成功来源一致;多人/多个请求压力下没有把版本接口抖动放大成强制重计算。
|
||||||
|
|
||||||
|
当前没有浏览器级完成证据,本 agent 未调用线上。详见 `docs/tasks/PROGRESS-rectification-cross-midnight-fix-20260920.md`。
|
||||||
@@ -160,7 +160,6 @@ import {
|
|||||||
readV9EngineScoringIdentity,
|
readV9EngineScoringIdentity,
|
||||||
cachedEngineScoreIsReusable,
|
cachedEngineScoreIsReusable,
|
||||||
toEngineEvents,
|
toEngineEvents,
|
||||||
v9EngineVersion,
|
|
||||||
executedMethodsFromLedger,
|
executedMethodsFromLedger,
|
||||||
runV9RangeReading,
|
runV9RangeReading,
|
||||||
runV9BlockScan,
|
runV9BlockScan,
|
||||||
@@ -900,7 +899,6 @@ export function createRectificationV9ReadOnlyTools(ctx: RectificationV9Context)
|
|||||||
|
|
||||||
export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
||||||
const { accounting, userId, caseId, turnId, attemptId, userMessage } = ctx;
|
const { accounting, userId, caseId, turnId, attemptId, userMessage } = ctx;
|
||||||
const engineVersion = v9EngineVersion();
|
|
||||||
let hasReadCase = false;
|
let hasReadCase = false;
|
||||||
let spokenPromptFailures = 0;
|
let spokenPromptFailures = 0;
|
||||||
let setFocusCompleted = 0;
|
let setFocusCompleted = 0;
|
||||||
@@ -934,7 +932,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
toolName: string,
|
toolName: string,
|
||||||
publicPhase: string,
|
publicPhase: string,
|
||||||
error: unknown,
|
error: unknown,
|
||||||
extra: { inputFingerprint?: string | null; engineVersion?: string | null } = {},
|
extra: { inputFingerprint?: string | null } = {},
|
||||||
) => {
|
) => {
|
||||||
const code = safeToolErrorCode(error);
|
const code = safeToolErrorCode(error);
|
||||||
const message = engineMessageForReceipt(error);
|
const message = engineMessageForReceipt(error);
|
||||||
@@ -947,7 +945,6 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
}));
|
}));
|
||||||
await receipt(toolName, publicPhase, "failed", {
|
await receipt(toolName, publicPhase, "failed", {
|
||||||
inputFingerprint: extra.inputFingerprint ?? null,
|
inputFingerprint: extra.inputFingerprint ?? null,
|
||||||
engineVersion: extra.engineVersion ?? null,
|
|
||||||
safeErrorCode: code,
|
safeErrorCode: code,
|
||||||
resultFingerprint: JSON.stringify({
|
resultFingerprint: JSON.stringify({
|
||||||
safe_error_code: code,
|
safe_error_code: code,
|
||||||
@@ -1925,7 +1922,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
execute: async (input) => {
|
execute: async (input) => {
|
||||||
assertCaseRef(input, caseId);
|
assertCaseRef(input, caseId);
|
||||||
const inputFingerprint = canonicalToolInputFingerprint("rectification-compare-candidates", input);
|
const inputFingerprint = canonicalToolInputFingerprint("rectification-compare-candidates", input);
|
||||||
await receipt("rectification-compare-candidates", "candidates.comparing", "started", { inputFingerprint, engineVersion });
|
await receipt("rectification-compare-candidates", "candidates.comparing", "started", { inputFingerprint });
|
||||||
try {
|
try {
|
||||||
const scored = await scoreAndPersistCurrentEvidence(input.caseId);
|
const scored = await scoreAndPersistCurrentEvidence(input.caseId);
|
||||||
const latest = scored.parsed.case.stage === "block_scan"
|
const latest = scored.parsed.case.stage === "block_scan"
|
||||||
@@ -1979,14 +1976,13 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
vedastro_validate_ms: scored.timings.vedastro_validate_ms,
|
vedastro_validate_ms: scored.timings.vedastro_validate_ms,
|
||||||
persist_ms: scored.timings.persist_ms,
|
persist_ms: scored.timings.persist_ms,
|
||||||
}),
|
}),
|
||||||
engineVersion: scored.persisted.algorithmVersion ?? engineVersion,
|
engineVersion: scored.persisted.algorithmVersion ?? null,
|
||||||
executedMethods: scored.score.executedMethods,
|
executedMethods: scored.score.executedMethods,
|
||||||
});
|
});
|
||||||
return { ...projection, executed_methods: scored.score.executedMethods };
|
return { ...projection, executed_methods: scored.score.executedMethods };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await failReceipt("rectification-compare-candidates", "candidates.comparing", error, {
|
await failReceipt("rectification-compare-candidates", "candidates.comparing", error, {
|
||||||
inputFingerprint,
|
inputFingerprint,
|
||||||
engineVersion,
|
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -2001,7 +1997,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
execute: async (input) => {
|
execute: async (input) => {
|
||||||
assertCaseRef(input, caseId);
|
assertCaseRef(input, caseId);
|
||||||
const inputFingerprint = canonicalToolInputFingerprint("rectification-read-diagnostics", input);
|
const inputFingerprint = canonicalToolInputFingerprint("rectification-read-diagnostics", input);
|
||||||
await receipt("rectification-read-diagnostics", "diagnostics.completed", "started", { inputFingerprint, engineVersion });
|
await receipt("rectification-read-diagnostics", "diagnostics.completed", "started", { inputFingerprint });
|
||||||
try {
|
try {
|
||||||
const dossier = await loadV9CaseDossier(accounting, userId, input.caseId);
|
const dossier = await loadV9CaseDossier(accounting, userId, input.caseId);
|
||||||
const parsed = parseDossierForTools(dossier);
|
const parsed = parseDossierForTools(dossier);
|
||||||
@@ -2034,7 +2030,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
|||||||
});
|
});
|
||||||
return { ...projection, executed_methods: diagnostics.executedMethods };
|
return { ...projection, executed_methods: diagnostics.executedMethods };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await failReceipt("rectification-read-diagnostics", "diagnostics.completed", error, { inputFingerprint, engineVersion });
|
await failReceipt("rectification-read-diagnostics", "diagnostics.completed", error, { inputFingerprint });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -109,7 +109,11 @@ test("unreachable versions retain the existing unknown-identity cache fallback,
|
|||||||
assert.equal(cachedEngineScoreIsReusable({ ...fingerprints, algorithmVersion: PREVIOUS }, fingerprints, live), true);
|
assert.equal(cachedEngineScoreIsReusable({ ...fingerprints, algorithmVersion: PREVIOUS }, fingerprints, live), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("new Case scoring writes the new version on both started and completed receipts", async (t) => {
|
async function runGoldenToolSequence(t: test.TestContext, options: {
|
||||||
|
compareVersion?: string;
|
||||||
|
diagnosticsVersion?: string;
|
||||||
|
diagnosticsFailure?: boolean;
|
||||||
|
} = {}) {
|
||||||
isolateIdentityEnv(t);
|
isolateIdentityEnv(t);
|
||||||
const request = golden.request;
|
const request = golden.request;
|
||||||
const candidateRange = { start_time: request.start_time, end_time: request.end_time };
|
const candidateRange = { start_time: request.start_time, end_time: request.end_time };
|
||||||
@@ -123,7 +127,13 @@ test("new Case scoring writes the new version on both started and completed rece
|
|||||||
const path = new URL(String(url)).pathname;
|
const path = new URL(String(url)).pathname;
|
||||||
calls.push(path);
|
calls.push(path);
|
||||||
if (path.endsWith("/versions")) return Response.json(golden.versions);
|
if (path.endsWith("/versions")) return Response.json(golden.versions);
|
||||||
if (path.endsWith("/score") || path.endsWith("/diagnostics")) return Response.json(golden.score);
|
if (path.endsWith("/score")) {
|
||||||
|
return Response.json({ ...golden.score, algorithm_version: options.compareVersion ?? CURRENT });
|
||||||
|
}
|
||||||
|
if (path.endsWith("/diagnostics")) {
|
||||||
|
if (options.diagnosticsFailure) return Response.json({ error: "fixture_unavailable" }, { status: 503 });
|
||||||
|
return Response.json({ ...golden.score, algorithm_version: options.diagnosticsVersion ?? CURRENT });
|
||||||
|
}
|
||||||
throw new Error(`unexpected engine request ${path}`);
|
throw new Error(`unexpected engine request ${path}`);
|
||||||
});
|
});
|
||||||
const accounting = fakeAccounting({
|
const accounting = fakeAccounting({
|
||||||
@@ -152,15 +162,44 @@ test("new Case scoring writes the new version on both started and completed rece
|
|||||||
});
|
});
|
||||||
const tools = createRectificationV9Tools({ userId: USER_ID, caseId: CASE_ID, turnId: TURN_ID, accounting: accounting.client as never });
|
const tools = createRectificationV9Tools({ userId: USER_ID, caseId: CASE_ID, turnId: TURN_ID, accounting: accounting.client as never });
|
||||||
for (const name of ["rectification-compare-candidates", "rectification-read-diagnostics"] as const) {
|
for (const name of ["rectification-compare-candidates", "rectification-read-diagnostics"] as const) {
|
||||||
await (tools[name] as unknown as { execute(input: unknown): Promise<unknown> }).execute({ caseId: CASE_ID });
|
const execute = () => (tools[name] as unknown as { execute(input: unknown): Promise<unknown> }).execute({ caseId: CASE_ID });
|
||||||
const receipts = accounting.calls.filter((call) => call.fn === "insert_agentic_rectification_tool_receipt" && call.args.p_tool_name === name);
|
if (options.diagnosticsFailure && name === "rectification-read-diagnostics") await assert.rejects(execute);
|
||||||
assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [
|
else await execute();
|
||||||
["started", CURRENT], ["completed", CURRENT],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
assert.ok(calls.includes("/api/rectification/v5/score"));
|
assert.ok(calls.includes("/api/rectification/v5/score"));
|
||||||
const persisted = accounting.calls.find((call) => call.fn === "persist_agentic_rectification_candidate_v2");
|
const persisted = accounting.calls.find((call) => call.fn === "persist_agentic_rectification_candidate_v2");
|
||||||
assert.equal(persisted?.args.p_algorithm_version, CURRENT);
|
assert.equal(persisted?.args.p_algorithm_version, options.compareVersion ?? CURRENT);
|
||||||
|
return accounting.calls.filter((call) => call.fn === "insert_agentic_rectification_tool_receipt");
|
||||||
|
}
|
||||||
|
|
||||||
|
test("new Case scoring writes actual identity only on completed receipts", async (t) => {
|
||||||
|
const receipts = await runGoldenToolSequence(t);
|
||||||
|
assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [
|
||||||
|
// 原值: started=CURRENT;新值: null;原因: BUG-984,开始不是成功结果身份。
|
||||||
|
["started", null], ["completed", CURRENT], ["started", null], ["completed", CURRENT],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("failed diagnostics do not claim an engine result identity", async (t) => {
|
||||||
|
const receipts = await runGoldenToolSequence(t, { diagnosticsFailure: true });
|
||||||
|
assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [
|
||||||
|
["started", null], ["completed", CURRENT], ["started", null], ["failed", null],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("BUG-984 blocker: real tool sequence permits mixed completed identities in one turn", async (t) => {
|
||||||
|
// Identity-only mutation models rolling backend versions; all response shape and values
|
||||||
|
// remain the real native golden. This is not a claim to have run future algorithms.
|
||||||
|
const version9 = "rectification-v5-matrix-scoring-9";
|
||||||
|
const version10 = "rectification-v5-matrix-scoring-10";
|
||||||
|
const receipts = await runGoldenToolSequence(t, { compareVersion: version9, diagnosticsVersion: version10 });
|
||||||
|
const completed = receipts.filter((call) => call.args.p_status === "completed");
|
||||||
|
assert.equal(new Set(completed.map((call) => call.args.p_turn_id)).size, 1);
|
||||||
|
assert.deepEqual(completed.map((call) => call.args.p_engine_version), [version9, version10]);
|
||||||
|
// Preserve the blocker as positive evidence, not a false passing acceptance test.
|
||||||
|
// F2 acceptance must replace this diagnostic once the approved aggregate fix lands.
|
||||||
|
assert.equal([version9, version10].sort().at(-1), version9);
|
||||||
|
assert.notEqual([version9, version10].sort().at(-1), completed.at(-1)?.args.p_engine_version);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("history opens with its bound Skill and reads old engine receipt values unchanged", async (t) => {
|
test("history opens with its bound Skill and reads old engine receipt values unchanged", async (t) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user