Skip to content

Commit 079f3c7

Browse files
Pigbibicodex
andcommitted
Enforce nonempty digest inverse invariants
Co-Authored-By: Codex <noreply@openai.com>
1 parent 294e30b commit 079f3c7

2 files changed

Lines changed: 424 additions & 0 deletions

File tree

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
from __future__ import annotations
2+
3+
import hashlib
4+
import json
5+
import re
6+
from collections.abc import Iterable, Mapping
7+
from dataclasses import dataclass
8+
from enum import Enum
9+
10+
11+
STATUS_VERSION = "pert.feed_status_canonical.v1"
12+
MAX_SAFE_JSON_INTEGER = 2**53 - 1
13+
MAX_ROWS_PER_FEED = 10_000
14+
EMPTY_DIGEST = hashlib.sha256(b"[]").hexdigest()
15+
_ROW_KEYS = ("item_id", "published_at", "source_type", "source_url", "author", "text")
16+
_OUTCOME_KEYS = frozenset({"feed_id", "feed_url", "kind", "state", "rows", "error_code"})
17+
_FEED_KEYS = frozenset(
18+
{"feed_id", "feed_url", "kind", "state", "accepted_row_count", "rejected_row_count", "row_digest", "error_code"}
19+
)
20+
_WIRE_KEYS = frozenset(
21+
{
22+
"status_version",
23+
"configured_feed_count",
24+
"feed_count",
25+
"successful_feed_count",
26+
"failed_feed_count",
27+
"quarantined_feed_count",
28+
"accepted_row_count",
29+
"rejected_row_count",
30+
"publication_complete",
31+
"eligible_for_live_publication",
32+
"aggregate_row_digest",
33+
"feeds",
34+
}
35+
)
36+
_ERROR_RE = re.compile(r"^[a-z][a-z0-9_]{0,63}$")
37+
_DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
38+
39+
40+
class DecisionContractError(ValueError):
41+
def __init__(self, code: str):
42+
super().__init__(code)
43+
self.code = code
44+
45+
46+
class DecisionKind(str, Enum):
47+
SUCCESS = "success"
48+
QUARANTINE = "quarantine"
49+
HARD_FAIL = "hard_fail"
50+
51+
52+
@dataclass(frozen=True)
53+
class ProducerDecision:
54+
kind: DecisionKind
55+
56+
57+
@dataclass(frozen=True)
58+
class CanonicalDecision:
59+
status_bytes: bytes
60+
decision: ProducerDecision
61+
62+
63+
def _fail(code: str) -> None:
64+
raise DecisionContractError(code)
65+
66+
67+
def _mapping(value: object, keys: frozenset[str], code: str) -> dict[str, object]:
68+
if not isinstance(value, Mapping):
69+
_fail(code)
70+
try:
71+
data = dict(value)
72+
except (AttributeError, KeyError, OverflowError, RuntimeError, TypeError, UnicodeError, ValueError):
73+
_fail(code)
74+
if set(data) != keys or any(type(key) is not str for key in data):
75+
_fail(code)
76+
return data
77+
78+
79+
def _string(value: object, code: str, *, allow_empty: bool = False) -> str:
80+
if type(value) is not str or (not allow_empty and not value) or any(ord(char) < 0x20 for char in value):
81+
_fail(code)
82+
return value
83+
84+
85+
def _integer(value: object, code: str) -> int:
86+
if type(value) is not int or value < 0 or value > MAX_SAFE_JSON_INTEGER:
87+
_fail(code)
88+
return value
89+
90+
91+
def _row(value: object) -> dict[str, str]:
92+
data = _mapping(value, frozenset(_ROW_KEYS), "row_invalid")
93+
return {key: _string(data[key], "row_invalid", allow_empty=key == "author") for key in _ROW_KEYS}
94+
95+
96+
def _digest(rows: list[dict[str, str]]) -> str:
97+
ordered = sorted(rows, key=lambda row: tuple(row[key] for key in _ROW_KEYS))
98+
try:
99+
payload = json.dumps(ordered, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
100+
except (RecursionError, TypeError, UnicodeError, ValueError):
101+
_fail("row_digest_invalid")
102+
return hashlib.sha256(payload).hexdigest()
103+
104+
105+
def _parse_outcome(value: object) -> tuple[dict[str, object], list[dict[str, str]]]:
106+
data = _mapping(value, _OUTCOME_KEYS, "outcome_invalid")
107+
kind = data["kind"]
108+
state = data["state"]
109+
if type(kind) is not str or kind not in {"rss2", "atom", "unknown"}:
110+
_fail("feed_kind_invalid")
111+
if type(state) is not str or state not in {"accepted", "failed", "quarantined"}:
112+
_fail("feed_state_invalid")
113+
rows_value = data["rows"]
114+
if not isinstance(rows_value, (list, tuple)) or len(rows_value) > MAX_ROWS_PER_FEED:
115+
_fail("rows_invalid")
116+
rows = [_row(item) for item in rows_value]
117+
error = data["error_code"]
118+
if error is not None and (type(error) is not str or not _ERROR_RE.fullmatch(error)):
119+
_fail("error_code_invalid")
120+
if state in {"accepted", "quarantined"} and kind not in {"rss2", "atom"}:
121+
_fail("feed_kind_invalid")
122+
if state == "accepted" and (not rows or error is not None):
123+
_fail("outcome_invariant_invalid")
124+
if state == "quarantined" and (rows or error is None):
125+
_fail("outcome_invariant_invalid")
126+
if state == "failed" and (rows or error is None):
127+
_fail("outcome_invariant_invalid")
128+
return {
129+
"feed_id": _string(data["feed_id"], "feed_invalid"),
130+
"feed_url": _string(data["feed_url"], "feed_invalid"),
131+
"kind": kind,
132+
"state": state,
133+
"error_code": error,
134+
}, rows
135+
136+
137+
def _canonical(value: object) -> bytes:
138+
try:
139+
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
140+
except (RecursionError, TypeError, UnicodeError, ValueError):
141+
_fail("status_serialization_invalid")
142+
143+
144+
def _build_wire(parsed: list[tuple[dict[str, object], list[dict[str, str]]]]) -> dict[str, object]:
145+
parsed.sort(key=lambda pair: (pair[0]["feed_id"], pair[0]["feed_url"]))
146+
accepted_rows = sorted(
147+
[row for data, rows in parsed if data["state"] == "accepted" for row in rows],
148+
key=lambda row: tuple(row[key] for key in _ROW_KEYS),
149+
)
150+
feeds = []
151+
for data, rows in parsed:
152+
accepted = data["state"] == "accepted"
153+
feeds.append(
154+
{
155+
"feed_id": data["feed_id"],
156+
"feed_url": data["feed_url"],
157+
"kind": data["kind"],
158+
"state": data["state"],
159+
"accepted_row_count": len(rows) if accepted else 0,
160+
"rejected_row_count": 0,
161+
"row_digest": _digest(rows) if accepted else EMPTY_DIGEST,
162+
"error_code": data["error_code"],
163+
}
164+
)
165+
failed = sum(data["state"] == "failed" for data, _ in parsed)
166+
quarantined = sum(data["state"] == "quarantined" for data, _ in parsed)
167+
complete = failed == 0 and quarantined == 0
168+
return {
169+
"status_version": STATUS_VERSION,
170+
"configured_feed_count": len(parsed),
171+
"feed_count": len(parsed),
172+
"successful_feed_count": len(parsed) - failed - quarantined,
173+
"failed_feed_count": failed,
174+
"quarantined_feed_count": quarantined,
175+
"accepted_row_count": len(accepted_rows),
176+
"rejected_row_count": 0,
177+
"publication_complete": complete,
178+
"eligible_for_live_publication": complete,
179+
"aggregate_row_digest": _digest(accepted_rows) if accepted_rows else EMPTY_DIGEST,
180+
"feeds": feeds,
181+
}
182+
183+
184+
def build_decision(validated_outcomes: Iterable[Mapping[str, object]]) -> CanonicalDecision:
185+
try:
186+
values = list(validated_outcomes)
187+
except (AttributeError, RuntimeError, TypeError, ValueError):
188+
_fail("outcomes_invalid")
189+
if not values:
190+
_fail("feed_config_empty")
191+
parsed = [_parse_outcome(value) for value in values]
192+
if len({data["feed_id"] for data, _ in parsed}) != len(parsed):
193+
_fail("feed_duplicate")
194+
status = _build_wire(parsed)
195+
kind = (
196+
DecisionKind.HARD_FAIL
197+
if status["failed_feed_count"]
198+
else DecisionKind.QUARANTINE
199+
if status["quarantined_feed_count"]
200+
else DecisionKind.SUCCESS
201+
)
202+
return CanonicalDecision(_canonical(status), ProducerDecision(kind))
203+
204+
205+
def _reject_duplicates(pairs: list[tuple[str, object]]) -> dict[str, object]:
206+
result: dict[str, object] = {}
207+
for key, value in pairs:
208+
if key in result:
209+
_fail("status_duplicate_key")
210+
result[key] = value
211+
return result
212+
213+
214+
def _validate_wire(value: object) -> dict[str, object]:
215+
data = _mapping(value, _WIRE_KEYS, "status_invalid")
216+
if data["status_version"] != STATUS_VERSION:
217+
_fail("status_version_invalid")
218+
counter_keys = _WIRE_KEYS - {
219+
"status_version",
220+
"publication_complete",
221+
"eligible_for_live_publication",
222+
"aggregate_row_digest",
223+
"feeds",
224+
}
225+
for key in counter_keys:
226+
_integer(data[key], "status_counter_invalid")
227+
for key in ("publication_complete", "eligible_for_live_publication"):
228+
if type(data[key]) is not bool:
229+
_fail("status_flag_invalid")
230+
aggregate = _string(data["aggregate_row_digest"], "status_digest_invalid")
231+
if not _DIGEST_RE.fullmatch(aggregate):
232+
_fail("status_digest_invalid")
233+
feeds = data["feeds"]
234+
if not isinstance(feeds, list) or not feeds:
235+
_fail("feed_invalid")
236+
previous: tuple[str, str] | None = None
237+
ids: set[str] = set()
238+
for value in feeds:
239+
item = _mapping(value, _FEED_KEYS, "feed_invalid")
240+
feed_id = _string(item["feed_id"], "feed_invalid")
241+
feed_url = _string(item["feed_url"], "feed_invalid")
242+
key = (feed_id, feed_url)
243+
if feed_id in ids:
244+
_fail("feed_duplicate")
245+
if previous is not None and key <= previous:
246+
_fail("feed_order_invalid")
247+
ids.add(feed_id)
248+
previous = key
249+
kind = item["kind"]
250+
state = item["state"]
251+
if type(kind) is not str or kind not in {"rss2", "atom", "unknown"}:
252+
_fail("feed_kind_invalid")
253+
if type(state) is not str or state not in {"accepted", "failed", "quarantined"}:
254+
_fail("feed_state_invalid")
255+
if state in {"accepted", "quarantined"} and kind not in {"rss2", "atom"}:
256+
_fail("feed_kind_invalid")
257+
accepted_count = _integer(item["accepted_row_count"], "feed_counter_invalid")
258+
rejected_count = _integer(item["rejected_row_count"], "feed_counter_invalid")
259+
if accepted_count > MAX_ROWS_PER_FEED:
260+
_fail("feed_counter_invalid")
261+
if rejected_count != 0:
262+
_fail("rejected_count_invalid")
263+
if state == "accepted" and accepted_count == 0:
264+
_fail("feed_state_invalid")
265+
if state != "accepted" and accepted_count != 0:
266+
_fail("feed_state_invalid")
267+
digest = _string(item["row_digest"], "status_digest_invalid")
268+
if not _DIGEST_RE.fullmatch(digest):
269+
_fail("status_digest_invalid")
270+
if state == "accepted" and digest == EMPTY_DIGEST:
271+
_fail("empty_digest_invalid")
272+
if state != "accepted" and digest != EMPTY_DIGEST:
273+
_fail("empty_digest_invalid")
274+
error = item["error_code"]
275+
if state == "accepted" and error is not None:
276+
_fail("feed_state_invalid")
277+
if state != "accepted" and (type(error) is not str or not _ERROR_RE.fullmatch(error)):
278+
_fail("feed_error_invalid")
279+
if data["configured_feed_count"] != len(feeds) or data["feed_count"] != len(feeds):
280+
_fail("status_counter_invalid")
281+
accepted = sum(item["state"] == "accepted" for item in feeds)
282+
failed = sum(item["state"] == "failed" for item in feeds)
283+
quarantined = sum(item["state"] == "quarantined" for item in feeds)
284+
accepted_rows = sum(item["accepted_row_count"] for item in feeds)
285+
rejected_rows = sum(item["rejected_row_count"] for item in feeds)
286+
if data["successful_feed_count"] != accepted or data["failed_feed_count"] != failed:
287+
_fail("status_counter_invalid")
288+
if data["quarantined_feed_count"] != quarantined or data["accepted_row_count"] != accepted_rows:
289+
_fail("status_counter_invalid")
290+
if rejected_rows != 0 or data["rejected_row_count"] != 0:
291+
_fail("rejected_count_invalid")
292+
if accepted_rows == 0 and aggregate != EMPTY_DIGEST:
293+
_fail("empty_digest_invalid")
294+
if accepted_rows > 0 and aggregate == EMPTY_DIGEST:
295+
_fail("empty_digest_invalid")
296+
complete = failed == 0 and quarantined == 0
297+
if data["publication_complete"] != complete or data["eligible_for_live_publication"] != complete:
298+
_fail("status_flag_invalid")
299+
return data
300+
301+
302+
def read_status(status_bytes: bytes) -> dict[str, object]:
303+
if type(status_bytes) is not bytes:
304+
_fail("status_bytes_invalid")
305+
try:
306+
value = json.loads(status_bytes.decode("utf-8"), object_pairs_hook=_reject_duplicates)
307+
except (UnicodeError, json.JSONDecodeError, RecursionError):
308+
_fail("status_bytes_invalid")
309+
data = _validate_wire(value)
310+
if _canonical(data) != status_bytes:
311+
_fail("status_noncanonical")
312+
return json.loads(status_bytes.decode("utf-8"))

0 commit comments

Comments
 (0)