fix: make RPM jitter exemption side-aware with one-shot room
Verifier adversarial cases showed pooled-room exemption could hide real breaches: edge rows may only borrow from the neighbor on their own side, and neighbor room is consumed globally so two windows cannot claim the same slot. Also parse SQLite created_at as UTC, guard the dispatch semaphore on progress-callback failure, and print a caveat that rescore live checks reflect current db3 state.
This commit is contained in:
+44
-21
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from collections import Counter
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -47,22 +47,50 @@ def inv_call_ids_unique(rows: list[Row]) -> None:
|
||||
|
||||
def _admit_second(row: Row, clock_offset_s: float) -> float:
|
||||
"""还原准入时刻(服务器钟): created_at 是完成落库时刻,减去调用延迟。"""
|
||||
done = datetime.fromisoformat(str(row["created_at"])).timestamp()
|
||||
dt = datetime.fromisoformat(str(row["created_at"]))
|
||||
if dt.tzinfo is None:
|
||||
# SQLite datetime('now') 落库为 UTC naive;按本机时区解析会错位整时
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
latency_ms = row.get("latency_ms") or 0
|
||||
return done - float(latency_ms) / 1000.0 + clock_offset_s
|
||||
return dt.timestamp() - float(latency_ms) / 1000.0 + clock_offset_s
|
||||
|
||||
|
||||
def _movable_edge_rows(
|
||||
admits: list[float], buckets: Counter[int], minute: int, limit: int, slack_s: float
|
||||
) -> int:
|
||||
"""超限窗口内可归邻窗的贴边行数(受邻窗余量约束)。"""
|
||||
near_edge = sum(
|
||||
1
|
||||
for a in admits
|
||||
if int(a // 60) == minute and min(a - minute * 60, (minute + 1) * 60 - a) <= slack_s
|
||||
)
|
||||
room = max(0, limit - buckets.get(minute - 1, 0)) + max(0, limit - buckets.get(minute + 1, 0))
|
||||
return min(near_edge, room)
|
||||
def _edge_counts(admits: list[float], minute: int, slack_s: float) -> tuple[int, int]:
|
||||
"""窗口内贴左界/贴右界(± slack_s)的行数。"""
|
||||
left = right = 0
|
||||
for a in admits:
|
||||
if int(a // 60) != minute:
|
||||
continue
|
||||
if a - minute * 60 <= slack_s:
|
||||
left += 1
|
||||
elif (minute + 1) * 60 - a <= slack_s:
|
||||
right += 1
|
||||
return left, right
|
||||
|
||||
|
||||
def _jitter_breaches(admits: list[float], limit: int, slack_s: float) -> dict[int, int]:
|
||||
"""贴边豁免的全局结算: 分侧借邻窗余量,余量一次性消耗不可重复认领。
|
||||
|
||||
verifier 对抗样例(2026-07-21)证明"合计余量 + 逐窗独立豁免"会漏报:
|
||||
左贴边行只可能属于左邻窗(反之亦然),且同一邻窗余量只能被认领一次。
|
||||
"""
|
||||
buckets = Counter(int(a // 60) for a in admits)
|
||||
consumed: Counter[int] = Counter()
|
||||
breaches: dict[int, int] = {}
|
||||
for minute in sorted(buckets):
|
||||
n = buckets[minute]
|
||||
if n <= limit:
|
||||
continue
|
||||
left_edge, right_edge = _edge_counts(admits, minute, slack_s)
|
||||
need = n - limit
|
||||
for edge, neighbor in ((left_edge, minute - 1), (right_edge, minute + 1)):
|
||||
room = max(0, limit - buckets.get(neighbor, 0)) - consumed[neighbor]
|
||||
take = min(edge, max(0, room), need)
|
||||
consumed[neighbor] += take
|
||||
need -= take
|
||||
if need > 0:
|
||||
breaches[minute] = n
|
||||
return breaches
|
||||
|
||||
|
||||
def inv_rpm_never_exceeded(
|
||||
@@ -89,13 +117,8 @@ def inv_rpm_never_exceeded(
|
||||
breaches: dict[tuple[str, int], int] = {}
|
||||
for source, admits in per_source.items():
|
||||
limit = per_source_rpm[source]
|
||||
buckets = Counter(int(a // 60) for a in admits)
|
||||
for minute, n in buckets.items():
|
||||
if n <= limit:
|
||||
continue
|
||||
movable = _movable_edge_rows(admits, buckets, minute, limit, boundary_slack_s)
|
||||
if n - movable > limit:
|
||||
breaches[(source, minute)] = n
|
||||
for minute, n in _jitter_breaches(admits, limit, boundary_slack_s).items():
|
||||
breaches[(source, minute)] = n
|
||||
assert not breaches, f"RPM 击穿(准入时刻+服务器钟口径): {dict(list(breaches.items())[:5])}"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user