fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock order. Cluster width, delivery, adoption, and reports keep the actual civil date; adopted date is stored separately from the reported birth_date. Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation thresholds, and Skill version are unchanged. Isolated Linux final-3 gates passed; four pre-existing Python failures remain. This is not a production release.
This commit is contained in:
@@ -17,6 +17,7 @@ four cached layers is proven in-process instead.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import math
|
||||
@@ -24,6 +25,7 @@ from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
from uuid import NAMESPACE_URL, uuid5
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -231,10 +233,68 @@ 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."""
|
||||
metadata = {
|
||||
"candidate_window_contract": "dated-v1",
|
||||
"candidate_intervals": [{"start_at": "1990-01-01T12:00", "end_at": "1990-01-01T12:02"}],
|
||||
"candidate_timezone_offset": 8,
|
||||
"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)
|
||||
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"
|
||||
# Independently spell out the result/candidate UUID namespace formula. Do not
|
||||
# call the production identity helper or accept any arbitrary UUID string.
|
||||
identity_request = {
|
||||
key: value for key, value in _normalized_request().items()
|
||||
if key not in {"asked_probe_keys", "dropped_asked_probe_keys", "declined_domains", "column_times", "refresh_probes"}
|
||||
}
|
||||
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"]
|
||||
candidate_id = uuid5(NAMESPACE_URL, f"rectification-candidate-policy-v3:{result_id}:{representative_time}")
|
||||
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)
|
||||
|
||||
|
||||
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_memoization_payloads(actual, expected)
|
||||
_assert_dated_payload_against_historical_golden(actual, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
("candidate_window_contract", "dated-v0"),
|
||||
("candidate_intervals", [{"start_at": "1990-01-02T12:00", "end_at": "1990-01-02T12:02"}]),
|
||||
("candidate_timezone_offset", 9),
|
||||
("candidate_timezone_id", "UTC"),
|
||||
("representative_candidate_id", "00000000-0000-4000-8000-000000000001"),
|
||||
("unapproved_metadata", True),
|
||||
("confirmation_allowed", True),
|
||||
])
|
||||
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 actual["decision_receipt"].get(field) != value
|
||||
actual["decision_receipt"][field] = value
|
||||
with pytest.raises(AssertionError):
|
||||
_assert_dated_payload_against_historical_golden(actual, expected)
|
||||
|
||||
|
||||
def test_golden_float_shift_of_1e_minus_2_fails() -> None:
|
||||
|
||||
Reference in New Issue
Block a user