Skip to content

Commit f9fd69e

Browse files
Pigbibicodex
andauthored
feat: reslice weekly contract path invariants (#26)
Co-authored-by: Codex <noreply@openai.com>
1 parent 075dc4f commit f9fd69e

2 files changed

Lines changed: 245 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Pure producer-owned contract for complete official PERT weekly inputs."""
2+
from __future__ import annotations
3+
4+
import json
5+
import re
6+
import unicodedata
7+
from collections.abc import Mapping
8+
from dataclasses import dataclass
9+
from datetime import date, datetime, timedelta, timezone
10+
from pathlib import PurePosixPath
11+
12+
SCHEMA_VERSION = "1"
13+
CONTRACT_VERSION = "political_event_weekly.v1"
14+
CADENCE = "weekly"
15+
MAX_SAFE_JSON_INTEGER = 2**53 - 1
16+
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
17+
_TIMESTAMP_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$")
18+
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
19+
_SHA1_RE = re.compile(r"^[0-9a-f]{40}$")
20+
_PATH_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]*$")
21+
_PROVENANCE_RE = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$")
22+
_KEYS = frozenset({"schema_version", "contract_version", "cadence", "as_of", "period_start", "period_end_exclusive", "generated_at", "run_mode", "producer_ref", "source_provenance", "source_artifacts", "feed_status"})
23+
_FEED_KEYS = frozenset({"feed_count", "successful_feed_count", "failed_feed_count", "stale_feed_count", "missing_feed_count", "complete"})
24+
_ARTIFACT_KEYS = frozenset({"path", "sha256", "row_count"})
25+
26+
27+
class WeeklyContractError(ValueError):
28+
def __init__(self, code: str) -> None:
29+
self.code = code
30+
super().__init__(code)
31+
32+
33+
def _invalid(code: str) -> WeeklyContractError:
34+
return WeeklyContractError(code)
35+
36+
37+
def _safe_int(value: object, code: str) -> int:
38+
if type(value) is not int or not 0 <= value <= MAX_SAFE_JSON_INTEGER:
39+
raise _invalid(code)
40+
return value
41+
42+
43+
def _canonical_path(value: object) -> str:
44+
if type(value) is not str or not value or not value.isascii() or unicodedata.normalize("NFC", value) != value or not _PATH_RE.fullmatch(value):
45+
raise _invalid("source_artifact_path_invalid")
46+
path = PurePosixPath(value)
47+
if path.is_absolute() or value != str(path) or value.endswith("/") or "//" in value or any(part in {"", ".", ".."} for part in path.parts):
48+
raise _invalid("source_artifact_path_invalid")
49+
return value
50+
51+
52+
@dataclass(frozen=True, slots=True)
53+
class WeeklySourceArtifact:
54+
path: str
55+
sha256: str
56+
row_count: int
57+
58+
def __post_init__(self) -> None:
59+
_canonical_path(self.path)
60+
if type(self.sha256) is not str or not _SHA256_RE.fullmatch(self.sha256):
61+
raise _invalid("source_artifact_invalid")
62+
_safe_int(self.row_count, "source_artifact_invalid")
63+
64+
65+
@dataclass(frozen=True, slots=True)
66+
class WeeklyFeedStatus:
67+
feed_count: int
68+
successful_feed_count: int
69+
failed_feed_count: int
70+
stale_feed_count: int
71+
missing_feed_count: int
72+
complete: bool
73+
74+
def __post_init__(self) -> None:
75+
counts = (self.feed_count, self.successful_feed_count, self.failed_feed_count, self.stale_feed_count, self.missing_feed_count)
76+
for count in counts:
77+
_safe_int(count, "feed_status_invalid")
78+
if type(self.complete) is not bool:
79+
raise _invalid("feed_status_invalid")
80+
if self.feed_count <= 0 or self.successful_feed_count != self.feed_count or any((self.failed_feed_count, self.stale_feed_count, self.missing_feed_count)) or not self.complete:
81+
raise _invalid("feed_status_incomplete")
82+
83+
84+
@dataclass(frozen=True, slots=True)
85+
class WeeklySourceContract:
86+
as_of: date
87+
period_start: date
88+
period_end_exclusive: date
89+
generated_at: datetime
90+
run_mode: str
91+
producer_ref: str
92+
source_provenance: str
93+
source_artifacts: tuple[WeeklySourceArtifact, ...]
94+
feed_status: WeeklyFeedStatus
95+
96+
def __post_init__(self) -> None:
97+
if type(self.as_of) is not date or type(self.period_start) is not date or type(self.period_end_exclusive) is not date or self.period_start.weekday() != 0 or self.period_end_exclusive != self.period_start + timedelta(days=7) or self.as_of != self.period_end_exclusive - timedelta(days=1):
98+
raise _invalid("period_invalid")
99+
if type(self.generated_at) is not datetime or self.generated_at.tzinfo != timezone.utc or self.generated_at < datetime.combine(self.period_end_exclusive, datetime.min.time(), timezone.utc):
100+
raise _invalid("generated_at_invalid")
101+
if self.run_mode not in {"scheduled", "manual"} or type(self.producer_ref) is not str or not _SHA1_RE.fullmatch(self.producer_ref) or type(self.source_provenance) is not str or not _PROVENANCE_RE.fullmatch(self.source_provenance):
102+
raise _invalid("provenance_invalid")
103+
if not isinstance(self.source_artifacts, tuple) or not self.source_artifacts or any(not isinstance(item, WeeklySourceArtifact) for item in self.source_artifacts):
104+
raise _invalid("source_artifact_invalid")
105+
ordered = tuple(sorted(self.source_artifacts, key=lambda item: item.path))
106+
if len({item.path for item in ordered}) != len(ordered):
107+
raise _invalid("source_artifact_duplicate")
108+
object.__setattr__(self, "source_artifacts", ordered)
109+
if not isinstance(self.feed_status, WeeklyFeedStatus):
110+
raise _invalid("feed_status_invalid")
111+
112+
113+
def _date(value: object) -> date:
114+
if type(value) is not str or not _DATE_RE.fullmatch(value):
115+
raise _invalid("date_invalid")
116+
try:
117+
return date.fromisoformat(value)
118+
except ValueError:
119+
raise _invalid("date_invalid") from None
120+
121+
122+
def _generated_at(value: object) -> datetime:
123+
if type(value) is not str or not _TIMESTAMP_RE.fullmatch(value):
124+
raise _invalid("generated_at_invalid")
125+
try:
126+
return datetime.fromisoformat(value[:-1] + "+00:00")
127+
except ValueError:
128+
raise _invalid("generated_at_invalid") from None
129+
130+
131+
def parse_weekly_contract(value: Mapping[str, object]) -> WeeklySourceContract:
132+
if not isinstance(value, Mapping) or set(value) != _KEYS:
133+
raise _invalid("contract_shape_invalid")
134+
if value["schema_version"] != SCHEMA_VERSION or value["contract_version"] != CONTRACT_VERSION or value["cadence"] != CADENCE:
135+
raise _invalid("contract_version_invalid")
136+
if type(value["run_mode"]) is not str or value["run_mode"] not in {"scheduled", "manual"}:
137+
raise _invalid("run_mode_invalid")
138+
source_artifacts = value["source_artifacts"]
139+
if not isinstance(source_artifacts, list) or not source_artifacts:
140+
raise _invalid("source_artifact_invalid")
141+
artifacts_list: list[WeeklySourceArtifact] = []
142+
for item in source_artifacts:
143+
if not isinstance(item, Mapping) or set(item) != _ARTIFACT_KEYS:
144+
raise _invalid("source_artifact_invalid")
145+
artifacts_list.append(WeeklySourceArtifact(item["path"], item["sha256"], item["row_count"]))
146+
artifacts = tuple(artifacts_list)
147+
status_value = value["feed_status"]
148+
if not isinstance(status_value, Mapping) or set(status_value) != _FEED_KEYS:
149+
raise _invalid("feed_status_invalid")
150+
status = WeeklyFeedStatus(status_value["feed_count"], status_value["successful_feed_count"], status_value["failed_feed_count"], status_value["stale_feed_count"], status_value["missing_feed_count"], status_value["complete"])
151+
return WeeklySourceContract(_date(value["as_of"]), _date(value["period_start"]), _date(value["period_end_exclusive"]), _generated_at(value["generated_at"]), value["run_mode"], value["producer_ref"], value["source_provenance"], artifacts, status)
152+
153+
154+
def serialize_weekly_contract(contract: WeeklySourceContract) -> bytes:
155+
if not isinstance(contract, WeeklySourceContract):
156+
raise _invalid("contract_type_invalid")
157+
payload = {"schema_version": SCHEMA_VERSION, "contract_version": CONTRACT_VERSION, "cadence": CADENCE, "as_of": contract.as_of.isoformat(), "period_start": contract.period_start.isoformat(), "period_end_exclusive": contract.period_end_exclusive.isoformat(), "generated_at": contract.generated_at.isoformat(timespec="microseconds").replace("+00:00", "Z"), "run_mode": contract.run_mode, "producer_ref": contract.producer_ref, "source_provenance": contract.source_provenance, "source_artifacts": [{"path": item.path, "sha256": item.sha256, "row_count": item.row_count} for item in contract.source_artifacts], "feed_status": {"feed_count": contract.feed_status.feed_count, "successful_feed_count": contract.feed_status.successful_feed_count, "failed_feed_count": contract.feed_status.failed_feed_count, "stale_feed_count": contract.feed_status.stale_feed_count, "missing_feed_count": contract.feed_status.missing_feed_count, "complete": contract.feed_status.complete}}
158+
parse_weekly_contract(payload)
159+
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")

tests/test_weekly_contract.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from datetime import date, datetime, timezone
5+
6+
import pytest
7+
8+
from political_event_tracking_research.weekly_contract import (
9+
MAX_SAFE_JSON_INTEGER,
10+
WeeklyContractError,
11+
WeeklyFeedStatus,
12+
WeeklySourceArtifact,
13+
WeeklySourceContract,
14+
parse_weekly_contract,
15+
serialize_weekly_contract,
16+
)
17+
18+
19+
def payload(**overrides: object) -> dict[str, object]:
20+
value: dict[str, object] = {
21+
"schema_version": "1", "contract_version": "political_event_weekly.v1", "cadence": "weekly",
22+
"as_of": "2026-07-12", "period_start": "2026-07-06", "period_end_exclusive": "2026-07-13",
23+
"generated_at": "2026-07-13T00:00:00.123456Z", "run_mode": "manual", "producer_ref": "a" * 40,
24+
"source_provenance": "official_political_event_tracking_research_v1",
25+
"source_artifacts": [{"path": "data/live/political_events.csv", "sha256": "b" * 64, "row_count": 11}],
26+
"feed_status": {"feed_count": 12, "successful_feed_count": 12, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True},
27+
}
28+
value.update(overrides)
29+
return value
30+
31+
32+
def test_parser_and_serializer_are_permutation_stable():
33+
items = [
34+
{"path": "z.csv", "sha256": "c" * 64, "row_count": 2},
35+
{"path": "a.csv", "sha256": "d" * 64, "row_count": 1},
36+
]
37+
first = parse_weekly_contract(payload(source_artifacts=items))
38+
second = parse_weekly_contract(payload(source_artifacts=list(reversed(items))))
39+
assert first == second
40+
assert serialize_weekly_contract(first) == serialize_weekly_contract(second)
41+
42+
43+
def test_direct_value_objects_canonicalize_and_round_trip():
44+
z = WeeklySourceArtifact("z.csv", "c" * 64, 2)
45+
a = WeeklySourceArtifact("a.csv", "d" * 64, 1)
46+
contract = WeeklySourceContract(
47+
date(2026, 7, 12), date(2026, 7, 6), date(2026, 7, 13), datetime(2026, 7, 13, tzinfo=timezone.utc),
48+
"manual", "a" * 40, "official_political_event_tracking_research_v1", (z, a), WeeklyFeedStatus(12, 12, 0, 0, 0, True),
49+
)
50+
assert [item.path for item in contract.source_artifacts] == ["a.csv", "z.csv"]
51+
assert parse_weekly_contract(json.loads(serialize_weekly_contract(contract))) == contract
52+
53+
54+
@pytest.mark.parametrize("path", ["", "/absolute", "./a.csv", "a/../b.csv", "a//b.csv", "a/b.csv/", "a\\b.csv", "a\tb.csv", "a\nb.csv", "é.csv", "a\x00b.csv"])
55+
def test_artifact_paths_use_ascii_safe_canonical_posix_allowlist(path):
56+
with pytest.raises(WeeklyContractError):
57+
parse_weekly_contract(payload(source_artifacts=[{"path": path, "sha256": "c" * 64, "row_count": 1}]))
58+
with pytest.raises(WeeklyContractError):
59+
WeeklySourceArtifact(path, "c" * 64, 1)
60+
61+
62+
@pytest.mark.parametrize("provenance", ["", "Official_PERT_v1", "official-pert-v1", "official_pert_é", "official.pert"])
63+
def test_provenance_is_canonical_ascii_identifier(provenance):
64+
with pytest.raises(WeeklyContractError):
65+
parse_weekly_contract(payload(source_provenance=provenance))
66+
67+
68+
def test_safe_integer_boundaries_and_bool_rejection():
69+
assert WeeklySourceArtifact("a.csv", "a" * 64, MAX_SAFE_JSON_INTEGER).row_count == MAX_SAFE_JSON_INTEGER
70+
for value in (-1, MAX_SAFE_JSON_INTEGER + 1, True):
71+
with pytest.raises(WeeklyContractError):
72+
WeeklySourceArtifact("a.csv", "a" * 64, value)
73+
with pytest.raises(WeeklyContractError):
74+
parse_weekly_contract(payload(feed_status={"feed_count": value, "successful_feed_count": value, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True}))
75+
76+
77+
def test_feed_status_is_typed_and_serializer_preserves_counters():
78+
status = WeeklyFeedStatus(12, 12, 0, 0, 0, True)
79+
assert parse_weekly_contract(json.loads(serialize_weekly_contract(parse_weekly_contract(payload(feed_status={"feed_count": 12, "successful_feed_count": 12, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True}))))) .feed_status == status
80+
81+
82+
def test_unknown_fields_and_contract_or_period_mismatch_fail_closed():
83+
with pytest.raises(WeeklyContractError):
84+
parse_weekly_contract(payload(extra=True))
85+
with pytest.raises(WeeklyContractError):
86+
parse_weekly_contract(payload(as_of="2026-07-13"))

0 commit comments

Comments
 (0)