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, so 57782aea changes
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: 57782aea changed 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:
Jesse_Chen
2026-10-02 12:31:50 +08:00
co-authored by Claude Opus 5.5
parent 57782aea8a
commit 3733b9787b
25 changed files with 51388 additions and 355 deletions
+47 -22
View File
@@ -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: