JuliusBrussee/caveman · error · CatalogError
: verified_at must include a timezone
Error message
{label}: verified_at must include a timezone What it means
verified_at parsed as a datetime/string but carries no timezone (naive datetime). The validator requires an explicit timezone so every row's date converts deterministically to UTC — provenance dates must be unambiguous across timezones. Thrown from parsed_time after successful parsing.
Solutions
- Append `Z` (or `+00:00`) to the timestamp: `2026-07-05T00:00:00Z`
- If the value is a local time, convert to UTC first, then add the offset
- Generate dates with timezone-aware code: `datetime.now(timezone.utc).isoformat()`
Example fix
# before verified_at: "2026-07-05T00:00:00" # after verified_at: "2026-07-05T00:00:00Z"
Defensive patterns
Strategy: validation
Validate before calling
from datetime import datetime
def has_timezone(value: str) -> bool:
try:
return datetime.fromisoformat(value.replace("Z", "+00:00")).tzinfo is not None
except ValueError:
return False Type guard
def is_aware_timestamp(value: object) -> bool:
if isinstance(value, datetime):
return value.tzinfo is not None
return isinstance(value, str) and has_timezone(value) Try / catch
try:
validate_catalog()
except CatalogError as e:
if "must include a timezone" in str(e):
append_utc_suffix(str(e)) Prevention
- Always end timestamps with `Z` or an explicit `+00:00` offset
- Use timezone-aware constructors: datetime.now(timezone.utc), never datetime.now()
- Treat naive ISO strings as invalid in code review
When it happens
Trigger: verified_at is a naive ISO string like `2026-07-05T00:00:00` (no Z or offset), or a quoted YAML date `"2026-07-05"`-style value parsed without tz info, or a YAML unquoted timestamp lacking an offset in a form PyYAML parses as naive.
Common situations: Writing `verified_at: 2026-07-05T12:00:00` and assuming UTC; local tooling generating naive isoformat() output (`datetime.now().isoformat()` instead of `datetime.now(timezone.utc).isoformat()`).
Related errors
- : verified_at must be an RFC3339 timestamp
- : verified_at must be RFC3339
- catalog is empty
- catalog
- : currency must be USD
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/503f15e25cbc7969.
Report an issue: GitHub.
Appendix: source
Thrown at shared/provider-catalog/validate_catalog.py:112
if not isinstance(document, list) or not document:
raise CatalogError(f"{path}: catalog must be a non-empty list")
if not all(isinstance(row, dict) for row in document):
raise CatalogError(f"{path}: every catalog row must be an object")
return document
def parsed_time(value: Any, label: str) -> datetime:
if isinstance(value, datetime):
parsed = value
elif isinstance(value, str):
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as error:
raise CatalogError(f"{label}: verified_at must be RFC3339") from error
else:
raise CatalogError(f"{label}: verified_at must be an RFC3339 timestamp")
if parsed.tzinfo is None:
raise CatalogError(f"{label}: verified_at must include a timezone")
return parsed.astimezone(timezone.utc)
def validate_row(row: dict[str, Any], label: str, now: datetime) -> tuple[str, str, str]:
unknown = set(row) - TOP_LEVEL_KEYS
missing = REQUIRED_KEYS - set(row)
if unknown:
raise CatalogError(f"{label}: unknown field(s): {', '.join(sorted(unknown))}")
if missing:
raise CatalogError(f"{label}: missing field(s): {', '.join(sorted(missing))}")
identity: list[str] = []
for field in ("provider", "model", "region"):
value = row[field]
if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
raise CatalogError(f"{label}: {field} must be a safe non-empty identifier")
identity.append(value)
if row["currency"] != "USD":View on GitHub (pinned to 3ee70a1026)