fix(rectification): keep compare requests valid after style cards (BUG-577–580)

Engine asked_probe_keys no longer include varga split hashes that 400 the scorer, failed compares become visible and retry, user stop can still deliver a range on a stale snapshot, and holdout no longer reasks domains already in the ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 15:46:33 +08:00
parent 3f4d38d485
commit 4e0db55f03
34 changed files with 1079 additions and 156 deletions
+58 -40
View File
@@ -1,47 +1,65 @@
from scripts.rectification_input_contract import (
candidate_input_fingerprint,
canonical_birth_input,
semantic_evidence_hash,
stability_probe_contract,
from __future__ import annotations
import unittest
from datetime import date
from scripts.rectification.contracts import normalize_rectification_request
EVENT_ID = "00000000-0000-4000-8000-000000000001"
VARGA_SPLIT_HASH = (
"04:45-05:15:04:47,04:51,04:53,04:59,05:00,05:06,05:08,05:13,05:15"
":varga.d9.04:47|04:51/05:00|05:06|04:59|04:53/05:08|05:13|05:15"
)
CASE = {
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 0,
"lat": 0.0,
"lon": 0.0,
"tz": 0.0,
}
def request():
return {
"birth_date": "1997-08-08",
"start_time": "05:13",
"end_time": "05:15",
"lat": 36.419,
"lon": 114.213,
"tz": 8,
"events": [{
"id": EVENT_ID,
"domain": "education",
"event_kind": "education_milestone",
"date_start": "2016-09-01",
"date_end": "2016-09-30",
"precision": "month",
"summary": "大学入学",
}],
}
def test_contract_uses_deployed_mean_node_default_and_stable_identity() -> None:
reordered = {key: CASE[key] for key in reversed(CASE)}
class RectificationInputContractTest(unittest.TestCase):
def test_canonical_varga_split_hash_is_128_characters(self):
self.assertEqual(len(VARGA_SPLIT_HASH), 128)
assert canonical_birth_input(CASE)["node_mode"] == "mean"
assert candidate_input_fingerprint(CASE) == candidate_input_fingerprint(reordered)
assert candidate_input_fingerprint(CASE) == candidate_input_fingerprint({**CASE, "nodeMode": "MEAN"})
def test_128_char_asked_probe_key_is_kept(self):
cleaned = normalize_rectification_request(
{**request(), "asked_probe_keys": [VARGA_SPLIT_HASH]},
today=date(2026, 7, 28),
)
self.assertIn(VARGA_SPLIT_HASH, cleaned["asked_probe_keys"])
self.assertNotIn("dropped_asked_probe_keys", cleaned)
def test_asked_probe_key_over_200_is_skipped_and_counted(self):
long_key = "k" * 201
cleaned = normalize_rectification_request(
{**request(), "asked_probe_keys": [long_key, "career.2018.05.dasha_boundary"]},
today=date(2026, 7, 28),
)
self.assertEqual(cleaned["asked_probe_keys"], ["career.2018.05.dasha_boundary"])
self.assertEqual(cleaned["dropped_asked_probe_keys"], 1)
def test_empty_asked_probe_key_is_still_rejected(self):
with self.assertRaises(ValueError):
normalize_rectification_request(
{**request(), "asked_probe_keys": [" "]},
today=date(2026, 7, 28),
)
def test_candidate_fingerprint_changes_with_calculation_input() -> None:
assert candidate_input_fingerprint(CASE) != candidate_input_fingerprint({**CASE, "minute": 1})
assert candidate_input_fingerprint(CASE) != candidate_input_fingerprint({**CASE, "node_mode": "true"})
def test_stability_contract_records_adjacent_probes_without_confirming() -> None:
contract = stability_probe_contract(CASE)
assert [probe["offset_minutes"] for probe in contract["probes"]] == [-5, -2, -1, 1, 2, 5]
assert contract["minute_confirmation_allowed"] is False
assert contract["status"] == "pending_score_comparison"
def test_semantic_hash_normalizes_only_known_order_insensitive_lists() -> None:
left = {"aspects": {"gives": ["Mars", "Saturn"]}, "ordered_scores": [2, 1]}
reordered_aspects = {"ordered_scores": [2, 1], "aspects": {"gives": ["Saturn", "Mars"]}}
reordered_scores = {"ordered_scores": [1, 2], "aspects": {"gives": ["Saturn", "Mars"]}}
assert semantic_evidence_hash(left) == semantic_evidence_hash(reordered_aspects)
assert semantic_evidence_hash(left) != semantic_evidence_hash(reordered_scores)
if __name__ == "__main__":
unittest.main()