fix(rectification): bump scoring identity to scoring-10 for functional profile v2; dated contract by generation (BUG-1181)
Functional roles feed the *_functional_*_auxiliary rules, so57782aeachanges candidate scores for identical input (memoization fixture 12:00: 8.6274 -> 8.4977). Per the "scoring semantics change => bump ALGORITHM_VERSION" precedent (scoring-7 -> 8 -> 9), the identity moves to scoring-10; policy v3, input contract v5 and Skill versions are unchanged, history is not relabeled. Five frontend sites and one SQL guard tested `=== "...scoring-9"` for the dated candidate-window contract; they now use isDatedScoringAlgorithmVersion / a generation regex (>= 9). Migration 20261002010000 only recreates validate_dated_rectification_candidate (one-line guard change). Memoization golden v2 written by the test's own write_golden; v1 (scoring-8) frozen by sha256. Real-engine scoring-10 cross-midnight golden added. Research records re-frozen per ERR-110 (label functional_v2_2026_10_02) and scripts/functional_benefics.py added to the frozen production identity (ERR-114:57782aeachanged scores without tripping it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
57782aea8a
commit
3733b9787b
@@ -1,6 +1,6 @@
|
||||
"""Memoization for candidate-minute invariants in the rectification engine.
|
||||
|
||||
Golden payload in tests/golden/rectification_engine_memoization_v1.json was
|
||||
Historical golden tests/golden/rectification_engine_memoization_v1.json was
|
||||
produced from origin/staging @ a8d29d1b before any memoization landed.
|
||||
On 2026-09-20 only two identity leaves were refreshed from the real engine:
|
||||
representative_candidate_id 85ca5490-07ab-54b7-acf0-59385f705015 ->
|
||||
@@ -8,6 +8,17 @@ e8c11277-022c-519c-95e9-6a0a04e8bf23, and snapshot algorithm_version
|
||||
rectification-v5-matrix-scoring-7 -> rectification-v5-matrix-scoring-8.
|
||||
The authorized cross-midnight version bump changes UUID identity, not this
|
||||
same-day fixture's scores. Historical numeric values and feature hashes remain.
|
||||
That file is now frozen (sha256 pinned below) and no longer the comparison
|
||||
target: scoring-8/9 scores belong to the old functional benefic / malefic
|
||||
grouping.
|
||||
|
||||
Current golden tests/golden/rectification_engine_memoization_v2.json
|
||||
(2026-10-02, BUG-1181) was written by ``write_golden`` from the real engine at
|
||||
57782aea (functional profile bphs_ch34_general_with_sign_exceptions_v2) with
|
||||
ALGORITHM_VERSION rectification-v5-matrix-scoring-10. Scores move for the same
|
||||
input (candidate 12:00: 8.6274 -> 8.4977), which is why the algorithm identity
|
||||
was bumped; it already carries the dated-v1 receipt metadata. Never rewrite it
|
||||
in place: a future scoring change adds a v3 file and freezes this one.
|
||||
|
||||
Do not compare that payload with a whole-structure ``==``. Cross-machine
|
||||
libm / pyswisseph rounding already drifted ``margin_percent`` by 1.1e-3
|
||||
@@ -47,10 +58,15 @@ import scripts.rectification.scoring_service as scoring_service
|
||||
import shadbala
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v1.json"
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v2.json"
|
||||
HISTORICAL_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v1.json"
|
||||
HISTORICAL_GOLDEN_SHA256 = "6266e448d7dad204b264cbe8f9bcf4c36e02286794764a94edb9e2ce4d0e3015"
|
||||
CURRENT_ALGORITHM_VERSION = "rectification-v5-matrix-scoring-10"
|
||||
FROZEN_TODAY = date(2026, 9, 16)
|
||||
TIMING_KEYS = frozenset({"column_compare_ms"})
|
||||
SOURCE_COMMIT = "a8d29d1b6cc37ff865ddec6c8bccdf9aa889ee53"
|
||||
HISTORICAL_SOURCE_COMMIT = "a8d29d1b6cc37ff865ddec6c8bccdf9aa889ee53"
|
||||
# Engine code of the v2 golden; the scoring-10 label is the BUG-1181 commit on top.
|
||||
SOURCE_COMMIT = "57782aea8ae28f5dd165a08d221ec27dbef391f8"
|
||||
CACHE_LAYER_KEYS = (
|
||||
"ashtakavarga_result",
|
||||
"shadbala_result",
|
||||
@@ -233,8 +249,8 @@ def _contexts_with_layer_cache_cleared(contexts: list[dict[str, Any]]) -> list[d
|
||||
return cleared
|
||||
|
||||
|
||||
def _assert_dated_payload_against_historical_golden(actual: dict[str, Any], expected: dict[str, Any]) -> None:
|
||||
"""Keep the scoring-8 golden immutable; validate exactly the scoring-9 additions."""
|
||||
def _assert_dated_payload_against_golden(actual: dict[str, Any], expected: dict[str, Any]) -> None:
|
||||
"""Compare with the scoring-10 golden; identity is spelled out, not trusted."""
|
||||
metadata = {
|
||||
"candidate_window_contract": "dated-v1",
|
||||
"candidate_intervals": [{"start_at": "1990-01-01T12:00", "end_at": "1990-01-01T12:02"}],
|
||||
@@ -242,14 +258,13 @@ def _assert_dated_payload_against_historical_golden(actual: dict[str, Any], expe
|
||||
"candidate_timezone_id": "",
|
||||
}
|
||||
receipt = actual["decision_receipt"]
|
||||
historical_receipt = expected["decision_receipt"]
|
||||
assert not set(metadata).intersection(historical_receipt), "historical fixture must not be relabeled"
|
||||
assert set(receipt) == set(historical_receipt) | set(metadata)
|
||||
golden_receipt = expected["decision_receipt"]
|
||||
assert set(receipt) == set(golden_receipt), f"receipt keys {set(receipt) ^ set(golden_receipt)!r}"
|
||||
for key, value in metadata.items():
|
||||
assert receipt[key] == value, key
|
||||
assert expected["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-8"
|
||||
assert actual["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-9"
|
||||
assert historical_receipt["representative_candidate_id"] == "e8c11277-022c-519c-95e9-6a0a04e8bf23"
|
||||
assert golden_receipt[key] == value, key
|
||||
assert expected["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
|
||||
assert actual["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
|
||||
# Independently spell out the result/candidate UUID namespace formula. Do not
|
||||
# call the production identity helper or accept any arbitrary UUID string.
|
||||
identity_request = {
|
||||
@@ -259,23 +274,33 @@ def _assert_dated_payload_against_historical_golden(actual: dict[str, Any], expe
|
||||
fingerprint = hashlib.sha256(json.dumps(
|
||||
identity_request, ensure_ascii=True, sort_keys=True, separators=(",", ":"),
|
||||
).encode()).hexdigest()
|
||||
result_id = uuid5(NAMESPACE_URL, f"rectification-v5-matrix-scoring-9:{fingerprint}")
|
||||
representative_time = historical_receipt["representative_time"]
|
||||
result_id = uuid5(NAMESPACE_URL, f"{CURRENT_ALGORITHM_VERSION}:{fingerprint}")
|
||||
representative_time = golden_receipt["representative_time"]
|
||||
candidate_id = uuid5(NAMESPACE_URL, f"rectification-candidate-policy-v3:{result_id}:{representative_time}")
|
||||
assert golden_receipt["representative_candidate_id"] == str(candidate_id)
|
||||
assert receipt["representative_candidate_id"] == str(candidate_id)
|
||||
projected = {**actual, "decision_receipt": {
|
||||
**{key: value for key, value in receipt.items() if key not in metadata},
|
||||
"representative_candidate_id": historical_receipt["representative_candidate_id"],
|
||||
}}
|
||||
# BUG-733/985: fingerprints hash unrounded floats; do not turn the
|
||||
# historical snapshot into a cross-process exact-hash contract.
|
||||
_assert_memoization_payloads(projected, expected)
|
||||
# snapshot into a cross-process exact-hash contract.
|
||||
_assert_memoization_payloads(actual, expected)
|
||||
|
||||
|
||||
def test_historical_scoring_8_golden_is_frozen_and_no_longer_current() -> None:
|
||||
"""BUG-1181: the old golden stays byte-identical; current scores moved, hence scoring-10."""
|
||||
raw = HISTORICAL_GOLDEN_PATH.read_bytes()
|
||||
assert hashlib.sha256(raw).hexdigest() == HISTORICAL_GOLDEN_SHA256
|
||||
historical = json.loads(raw)
|
||||
assert historical["source_commit"] == HISTORICAL_SOURCE_COMMIT
|
||||
assert historical["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-8"
|
||||
current = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
assert current["source_commit"] == SOURCE_COMMIT
|
||||
with pytest.raises(AssertionError):
|
||||
_assert_memoization_payloads(current, historical)
|
||||
|
||||
|
||||
def test_score_candidates_matches_baseline_golden() -> None:
|
||||
expected = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
actual = json.loads(json.dumps(_golden_payload(), ensure_ascii=True))
|
||||
_assert_dated_payload_against_historical_golden(actual, expected)
|
||||
_assert_dated_payload_against_golden(actual, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
@@ -290,11 +315,11 @@ def test_score_candidates_matches_baseline_golden() -> None:
|
||||
def test_dated_golden_projection_rejects_metadata_identity_and_gate_mutations(field, value) -> None:
|
||||
expected = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
actual = json.loads(json.dumps(_golden_payload(), ensure_ascii=True))
|
||||
_assert_dated_payload_against_historical_golden(actual, expected)
|
||||
_assert_dated_payload_against_golden(actual, expected)
|
||||
assert actual["decision_receipt"].get(field) != value
|
||||
actual["decision_receipt"][field] = value
|
||||
with pytest.raises(AssertionError):
|
||||
_assert_dated_payload_against_historical_golden(actual, expected)
|
||||
_assert_dated_payload_against_golden(actual, expected)
|
||||
|
||||
|
||||
def test_golden_float_shift_of_1e_minus_2_fails() -> None:
|
||||
|
||||
Reference in New Issue
Block a user