fix: align rectification spec hashes across runtimes

This commit is contained in:
Jesse_Chen
2026-07-29 22:49:17 +08:00
parent 555e7d9bef
commit a5f409a500
3 changed files with 34 additions and 2 deletions
+14
View File
@@ -1662,3 +1662,17 @@
- 防复发:模型调用成功、Schema 成功和问题被接受必须分开观测;Opportunity utility 不得把历史关键词与“未覆盖领域”组合成伪装的固定轮询。
- 相关记录:BUG-086、BUG-090、BUG-091
- 修复版本:`birth-time-rectification-v6` / `rectification-agent-v6-1`
## BUG-093 | 首次候选评分因跨语言计算规格哈希不一致而失败
- 状态:resolved
- 首次发现:2026-07-29
- 最近更新:2026-07-29
- 影响面:V5 Candidate Engine 首次达到三事件评分门后的 Feature Snapshot 原子持久化
- 用户现象:第三件可评分事件已经保存在 Turn,但 Worker 最终显示“这次比较没有完成,回答已经保留,请再试一次”,Case 恢复上一问题且没有写入新事件。
- 根因:服务器创建 Case 时由 TypeScript 对整数时区 `8` 计算规格哈希;Python 请求归一化把它变成浮点数 `8.0`,而 Python JSON 序列化保留 `.0`。两个运行时对语义相同的规格得到不同哈希,完成事务因此拒绝 Feature Snapshot 并抛出 `rectification_v5_feature_snapshot_mismatch`
- 修复:Python 生成跨服务 Calculation Spec 时将整数值的经纬度和时区规范化为整数,使其 JSON 数字表示与 TypeScript `JSON.stringify` 一致;评分算法和候选矩阵不变。
- 验证:新增已知 TypeScript 哈希向量测试,修复前稳定失败、修复后通过;staging Case `e2d3e1d2-efb0-461d-9914-f890bc2b8569` 的 PostgreSQL 日志确认原始异常,使用同一规格重放确认 Python Feature Snapshot 哈希恢复为 Case 哈希。
- 防复发:跨语言持久化指纹必须使用已知向量验证 JSON 数字规范化,不能只在各自语言内断言自洽。
- 相关记录:BUG-087、BUG-092
- 修复版本:`rectification-v5-matrix-scoring-1`(仅修复输入规范化,算法版本不变)
+6 -1
View File
@@ -150,11 +150,16 @@ def score_from_matrix(request: RectificationRequest, built: dict[str, Any]) -> l
def calculation_spec(request: RectificationRequest) -> dict[str, Any]:
def json_number(value: float) -> int | float:
return int(value) if value.is_integer() else value
return {
"version": INPUT_CONTRACT_VERSION,
"birthDate": request["birth_date"],
"candidateRange": {"start": request["start_time"], "end": request["end_time"]},
"latitude": request["lat"], "longitude": request["lon"], "timezoneOffsetHours": request["tz"],
"latitude": json_number(request["lat"]),
"longitude": json_number(request["lon"]),
"timezoneOffsetHours": json_number(request["tz"]),
"ayanamsa": "lahiri", "nodeMode": "mean", "minuteStep": 1,
}
+14 -1
View File
@@ -6,7 +6,13 @@ from unittest.mock import patch
from scripts.rectification.api_service import diagnostics, score_candidates
from scripts.rectification.contracts import normalize_rectification_request
from scripts.rectification.scoring_service import build_event_contribution_matrix, sample_event_dates, score_from_matrix
from scripts.rectification.scoring_service import (
build_event_contribution_matrix,
calculation_spec,
sample_event_dates,
score_from_matrix,
sha256,
)
from scripts.jyotish_api_server import (
API_COMMAND_MAP,
TECHNIQUE_EXAMPLE_ENDPOINTS,
@@ -38,6 +44,13 @@ def request(*, precision: str = "month", event_kind: str = "education_milestone"
class RectificationV5ServicesTest(unittest.TestCase):
def test_calculation_spec_hash_matches_typescript_for_integral_timezone(self):
normalized = normalize_rectification_request(request(), today=date(2026, 7, 28))
self.assertEqual(
sha256(calculation_spec(normalized)),
"f05fe0f56ef9ba2b18ec3c6c54f1649f06f1ae5a926491a5c5f676d718d92865",
)
def test_shared_validator_rejects_family_and_non_self_health_scoring(self):
with self.assertRaisesRegex(ValueError, "domain is not scoreable"):
normalize_rectification_request(request(domain="family", event_kind="family_bereavement"), today=date(2026, 7, 28))