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:
@@ -218,7 +218,7 @@ def score_candidates(request: RectificationRequest) -> dict[str, Any]:
|
||||
fingerprint = sha256({
|
||||
key: value
|
||||
for key, value in request.items()
|
||||
if key != "asked_probe_keys"
|
||||
if key not in {"asked_probe_keys", "dropped_asked_probe_keys"}
|
||||
})
|
||||
result_id = str(uuid5(NAMESPACE_URL, f"{ALGORITHM_VERSION}:{fingerprint}"))
|
||||
candidate_decisions = build_candidate_decisions(
|
||||
|
||||
@@ -54,6 +54,7 @@ _REQUEST_FIELDS = frozenset({
|
||||
"birth_date", "start_time", "end_time", "lat", "lon", "tz", "events",
|
||||
"ayanamsa", "node_mode", "asked_probe_keys", "minute_step", "blocks",
|
||||
}) | _REQUEST_PROVENANCE_FIELDS
|
||||
ASKED_PROBE_KEY_MAX_LENGTH = 200
|
||||
_EVENT_FIELDS = frozenset({"id", "domain", "event_kind", "date_start", "date_end", "precision", "summary"}) | _EVENT_PROVENANCE_FIELDS
|
||||
_CLOCK = re.compile(r"(?:[01]\d|2[0-3]):[0-5]\d\Z")
|
||||
_MINUTES_PER_DAY = 24 * 60
|
||||
@@ -167,6 +168,7 @@ class RectificationRequest(TypedDict):
|
||||
timezone_source: NotRequired[str | None]
|
||||
local_time_status: NotRequired[str | None]
|
||||
asked_probe_keys: NotRequired[list[str]]
|
||||
dropped_asked_probe_keys: NotRequired[int]
|
||||
minute_step: NotRequired[int]
|
||||
blocks: NotRequired[list[dict[str, Any]]]
|
||||
|
||||
@@ -335,17 +337,23 @@ def normalize_rectification_request(body: Any, *, today: date | None = None) ->
|
||||
raise ValueError("asked_probe_keys must contain between 0 and 200 strings")
|
||||
cleaned_keys: list[str] = []
|
||||
seen: set[str] = set()
|
||||
dropped = 0
|
||||
for index, item in enumerate(asked):
|
||||
if not isinstance(item, str) or not item.strip() or len(item.strip()) > 120:
|
||||
if not isinstance(item, str) or not item.strip():
|
||||
raise ValueError(
|
||||
f"asked_probe_keys[{index}] must be a non-empty string up to 120 characters"
|
||||
f"asked_probe_keys[{index}] must be a non-empty string up to {ASKED_PROBE_KEY_MAX_LENGTH} characters"
|
||||
)
|
||||
key = item.strip()
|
||||
if len(key) > ASKED_PROBE_KEY_MAX_LENGTH:
|
||||
dropped += 1
|
||||
continue
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
cleaned_keys.append(key)
|
||||
cleaned_request["asked_probe_keys"] = cleaned_keys
|
||||
if dropped:
|
||||
cleaned_request["dropped_asked_probe_keys"] = dropped
|
||||
if "minute_step" in body:
|
||||
minute_step = body.get("minute_step")
|
||||
if isinstance(minute_step, bool) or not isinstance(minute_step, int) or not 1 <= minute_step <= 15:
|
||||
|
||||
@@ -706,6 +706,7 @@ def build_decision_receipt(
|
||||
"candidate_contrast_opportunities": packet.get("candidate_contrast_opportunities") or [],
|
||||
"holdout_validation_probes": packet.get("holdout_validation_probes") or [],
|
||||
"dropped_probes": packet.get("dropped_probes") or [],
|
||||
"dropped_asked_probe_keys": int(request.get("dropped_asked_probe_keys") or 0),
|
||||
"prospective_probes": packet.get("prospective_probes") or [],
|
||||
"horary_observation": build_horary_observation(request),
|
||||
"unique_minute_claim": False,
|
||||
|
||||
Reference in New Issue
Block a user