Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
393 changes: 393 additions & 0 deletions src/quant_platform_kit/data/research_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
"""Pure validation and canonicalization for research-input manifest v1.

The runtime must provide an IANA timezone database through the operating
system or the ``tzdata`` package; validation fails closed when it is absent.
"""

from __future__ import annotations

from collections.abc import Mapping as _Mapping
from datetime import date as _date
from datetime import datetime as _datetime
from datetime import timezone as _datetime_timezone
from decimal import Decimal as _Decimal
import hashlib as _hashlib
import json as _json
import re as _re
from zoneinfo import ZoneInfo as _ZoneInfo
from zoneinfo import available_timezones as _available_timezones

del annotations

__all__ = [
"InvalidResearchInputEvidence",
"validate_research_input_manifest",
"canonical_research_input_manifest_bytes",
"research_input_manifest_sha256",
"read_research_input_manifest_json",
]


_TOP_LEVEL_REQUIRED = frozenset(
{
"schema_version",
"manifest_id",
"research_input_contract_id",
"domain",
"profile",
"artifact_type",
"observed_at",
"effective_at",
"as_of",
"producer",
"calendar",
"adjustment",
"sources",
"members",
}
)
_PRODUCER_REQUIRED = frozenset(
{"repository", "commit_sha", "tree_sha", "tool", "tool_version"}
)
_CALENDAR_REQUIRED = frozenset(
{"calendar_id", "timezone", "session_date", "source", "source_revision"}
)
_ADJUSTMENT_REQUIRED = frozenset({"policy", "source", "source_revision"})
_SOURCE_REQUIRED = frozenset(
{"source_id", "revision", "observed_at", "content_sha256"}
)
_MEMBER_REQUIRED = frozenset({"path", "media_type", "size_bytes", "sha256"})
_ADJUSTMENT_POLICIES = frozenset(
{"raw", "split_adjusted", "total_return_adjusted"}
)
_COMMIT_PATTERN = _re.compile(r"[0-9a-f]{40}")
_SHA256_PATTERN = _re.compile(r"[0-9a-f]{64}")
_DATE_PATTERN = _re.compile(r"\d{4}-\d{2}-\d{2}")
_TIMESTAMP_PATTERN = _re.compile(
r"(?P<whole>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})"
r"(?:\.(?P<fraction>\d+))?"
r"(?P<offset>Z|[+-]\d{2}:\d{2})"
)
_EPOCH = _datetime(1970, 1, 1, tzinfo=_datetime_timezone.utc)
_HOST_LOCAL_TIMEZONE_KEYS = frozenset({"Factory", "localtime", "posixrules"})
_HOST_LOCAL_TIMEZONE_PREFIXES = ("posix/", "right/")


class InvalidResearchInputEvidence(ValueError):
"""The manifest is not strict, canonical research-input evidence."""


def _fail(message: str) -> None:
raise InvalidResearchInputEvidence(message)


def _object(
value: object,
required: frozenset[str],
optional: frozenset[str] = frozenset(),
) -> _Mapping[str, object]:
if not isinstance(value, _Mapping):
_fail("expected an object")
if any(type(key) is not str for key in value):
_fail("object keys must be strings")
keys = frozenset(value)
if not required <= keys or not keys <= required | optional:
_fail("object fields do not match the frozen contract")
return value


def _nonempty(value: object) -> str:
if type(value) is not str or not value.strip():
Comment thread
Pigbibi marked this conversation as resolved.
_fail("expected a non-empty string")
if any("\ud800" <= character <= "\udfff" for character in value):
_fail("strings must contain only Unicode scalar values")
return value


def _matches(value: object, pattern: _re.Pattern[str]) -> str:
text = _nonempty(value)
if pattern.fullmatch(text) is None:
_fail("string does not match the frozen format")
return text


def _timestamp(value: object) -> tuple[str, _Decimal]:
text = _nonempty(value)
match = _TIMESTAMP_PATTERN.fullmatch(text)
if match is None:
_fail("timestamp must use strict ISO-8601 date-time syntax")
if match.group("offset") == "-00:00":
_fail("timestamp must not use an unknown UTC offset")
try:
parsed = _datetime.fromisoformat(
text[:-1] + "+00:00" if text.endswith("Z") else text
)
Comment on lines +122 to +124

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support all permitted fractions on Python 3.10

On Python 3.10, which the package declares as supported, datetime.fromisoformat() only accepts fractional seconds with three or six digits, while this contract's regex and schema permit any non-empty digit sequence. Consequently valid timestamps such as .1Z, .12Z, or nanosecond .123456789Z are rejected on Python 3.10 but accepted on Python 3.11, making manifest validity interpreter-dependent; validate the whole-second portion separately from the unrestricted fraction.

Useful? React with 👍 / 👎.

if parsed.tzinfo is None or parsed.utcoffset() is None:
_fail("timestamp must be timezone-aware")
whole_utc = parsed.replace(microsecond=0).astimezone(
_datetime_timezone.utc
)
delta = whole_utc - _EPOCH
exact = _Decimal(delta.days * 86_400 + delta.seconds)
fraction = match.group("fraction")
if fraction is not None:
exact += _Decimal(f"0.{fraction}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compare timestamp fractions without Decimal rounding

When a timestamp has more fractional digits than the active decimal context can retain, adding the fraction to the epoch-second integer rounds it before comparison. With the default precision, for example, as_of = 2026-07-30T09:00:00.00000000000000000001Z and observed_at = 2026-07-30T09:00:00.00000000000000000009Z both become Decimal('1785402000.000000000000000000'), so the later observation is accepted and can leak future data. Fresh evidence against the earlier remediation is that its new Decimal addition remains context-limited despite the unrestricted \d+ fraction; compare a lossless representation or reject excess precision.

Useful? React with 👍 / 👎.

except InvalidResearchInputEvidence:
raise
except Exception:
_fail("timestamp must be valid ISO-8601")
return text, exact


def _calendar_date(value: object) -> str:
text = _nonempty(value)
if _DATE_PATTERN.fullmatch(text) is None:
_fail("session_date must use YYYY-MM-DD")
try:
_date.fromisoformat(text)
except Exception:
_fail("session_date must be a valid ISO date")
return text


def _timezone(value: object) -> str:
text = _nonempty(value)
try:
available = _available_timezones()
except Exception:
_fail("IANA timezone database is unavailable")
if not available:
_fail("IANA timezone database is unavailable")
if (
text in _HOST_LOCAL_TIMEZONE_KEYS
or text.startswith(_HOST_LOCAL_TIMEZONE_PREFIXES)
or text not in available
):
_fail("timezone must be a valid IANA timezone")
try:
_ZoneInfo(text)
except Exception:
_fail("IANA timezone database cannot load the requested timezone")
return text


def _member_path(value: object) -> str:
path = _nonempty(value)
if path.startswith("/") or "\\" in path or "\x00" in path:
_fail("member path must be relative POSIX")
if any(part in {"", ".", ".."} for part in path.split("/")):
_fail("member path contains an unsafe segment")
return path


def _canonical_order(value: object) -> object:
if isinstance(value, dict):
return {key: _canonical_order(value[key]) for key in sorted(value)}
if isinstance(value, list):
return [_canonical_order(child) for child in value]
return value


def _validate(manifest: _Mapping[str, object]) -> dict[str, object]:
root = _object(
manifest,
_TOP_LEVEL_REQUIRED,
frozenset({"parent_manifest_sha256"}),
)
if root["schema_version"] != "research_input_manifest.v1":
_fail("schema_version is invalid")

result: dict[str, object] = {
"schema_version": "research_input_manifest.v1",
"manifest_id": _nonempty(root["manifest_id"]),
"research_input_contract_id": _nonempty(
root["research_input_contract_id"]
),
"domain": _nonempty(root["domain"]),
"profile": _nonempty(root["profile"]),
"artifact_type": _nonempty(root["artifact_type"]),
}
observed_text, observed_at = _timestamp(root["observed_at"])
effective_text, effective_at = _timestamp(root["effective_at"])
as_of_text, as_of = _timestamp(root["as_of"])
if observed_at > as_of or effective_at > as_of:
_fail("manifest timestamps exceed as_of")
result.update(
{
"observed_at": observed_text,
"effective_at": effective_text,
"as_of": as_of_text,
}
)

producer = _object(root["producer"], _PRODUCER_REQUIRED)
result["producer"] = {
"repository": _nonempty(producer["repository"]),
"commit_sha": _matches(producer["commit_sha"], _COMMIT_PATTERN),
"tree_sha": _matches(producer["tree_sha"], _COMMIT_PATTERN),
"tool": _nonempty(producer["tool"]),
"tool_version": _nonempty(producer["tool_version"]),
}

calendar = _object(root["calendar"], _CALENDAR_REQUIRED)
result["calendar"] = {
"calendar_id": _nonempty(calendar["calendar_id"]),
"timezone": _timezone(calendar["timezone"]),
"session_date": _calendar_date(calendar["session_date"]),
"source": _nonempty(calendar["source"]),
"source_revision": _nonempty(calendar["source_revision"]),
}

adjustment = _object(root["adjustment"], _ADJUSTMENT_REQUIRED)
policy = _nonempty(adjustment["policy"])
if policy not in _ADJUSTMENT_POLICIES:
_fail("adjustment policy is invalid")
result["adjustment"] = {
"policy": policy,
"source": _nonempty(adjustment["source"]),
"source_revision": _nonempty(adjustment["source_revision"]),
}

sources_value = root["sources"]
if type(sources_value) is not list or not sources_value:
_fail("sources must be a non-empty array")
sources: list[dict[str, object]] = []
source_ids: list[str] = []
for source_value in sources_value:
source = _object(source_value, _SOURCE_REQUIRED)
source_id = _nonempty(source["source_id"])
source_observed_text, source_observed_at = _timestamp(
source["observed_at"]
)
if source_observed_at > as_of:
_fail("source observed_at exceeds as_of")
source_ids.append(source_id)
sources.append(
{
"source_id": source_id,
"revision": _nonempty(source["revision"]),
"observed_at": source_observed_text,
"content_sha256": _matches(
source["content_sha256"], _SHA256_PATTERN
),
}
)
if source_ids != sorted(source_ids) or len(source_ids) != len(set(source_ids)):
_fail("sources must be strictly ordered and unique by source_id")
result["sources"] = sources

members_value = root["members"]
if type(members_value) is not list or not members_value:
_fail("members must be a non-empty array")
members: list[dict[str, object]] = []
member_paths: list[str] = []
for member_value in members_value:
member = _object(member_value, _MEMBER_REQUIRED)
path = _member_path(member["path"])
size_bytes = member["size_bytes"]
if type(size_bytes) is not int or size_bytes < 0:
_fail("size_bytes must be a non-negative integer")
member_paths.append(path)
members.append(
{
"path": path,
"media_type": _nonempty(member["media_type"]),
"size_bytes": size_bytes,
"sha256": _matches(member["sha256"], _SHA256_PATTERN),
}
)
if member_paths != sorted(member_paths) or len(member_paths) != len(
set(member_paths)
):
_fail("members must be strictly ordered and unique by path")
result["members"] = members

if "parent_manifest_sha256" in root:
result["parent_manifest_sha256"] = _matches(
root["parent_manifest_sha256"], _SHA256_PATTERN
)
return _canonical_order(result)


def validate_research_input_manifest(
manifest: _Mapping[str, object],
) -> dict[str, object]:
Comment thread
Pigbibi marked this conversation as resolved.
"""Validate and return an independent canonical-order manifest.

The runtime must provide a system tzdb or the ``tzdata`` package.
"""
try:
return _validate(manifest)
except InvalidResearchInputEvidence:
raise
except Exception:
raise InvalidResearchInputEvidence("invalid research-input manifest") from None


def canonical_research_input_manifest_bytes(
manifest: _Mapping[str, object],
) -> bytes:
"""Return strict canonical UTF-8 JSON bytes without a trailing newline."""
try:
validated = validate_research_input_manifest(manifest)
return _json.dumps(
validated,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
except InvalidResearchInputEvidence:
raise
except Exception:
raise InvalidResearchInputEvidence("cannot canonicalize manifest") from None


def research_input_manifest_sha256(manifest: _Mapping[str, object]) -> str:
"""Return the SHA-256 digest of strict canonical manifest bytes."""
try:
return _hashlib.sha256(
canonical_research_input_manifest_bytes(manifest)
).hexdigest()
except InvalidResearchInputEvidence:
raise
except Exception:
raise InvalidResearchInputEvidence("cannot digest manifest") from None


def _reject_duplicate_keys(
pairs: list[tuple[str, object]],
) -> dict[str, object]:
result: dict[str, object] = {}
for key, value in pairs:
if key in result:
_fail("duplicate JSON object key")
result[key] = value
return result


def _reject_nonfinite(_value: str) -> object:
_fail("non-finite JSON constants are forbidden")


def read_research_input_manifest_json(payload: bytes | str) -> dict[str, object]:
"""Strictly parse and validate one JSON object from bytes or text."""
try:
if type(payload) is bytes:
text = payload.decode("utf-8")
elif type(payload) is str:
text = payload
else:
_fail("payload must be bytes or str")
parsed = _json.loads(
text,
object_pairs_hook=_reject_duplicate_keys,
parse_constant=_reject_nonfinite,
)
if not isinstance(parsed, dict):
_fail("manifest JSON root must be an object")
return validate_research_input_manifest(parsed)
except InvalidResearchInputEvidence:
raise
except Exception:
raise InvalidResearchInputEvidence("invalid research-input JSON") from None
Loading