JuliusBrussee/caveman · error · CatalogError

: verified_at must be an RFC3339 timestamp

Error message

{label}: verified_at must be an RFC3339 timestamp

What it means

verified_at is neither a YAML-parsed datetime nor a string — it is some other type (int, float, list, bool, etc.). parsed_time only accepts datetime objects or RFC3339 strings, so any other type is rejected outright. This keeps price provenance a real timestamp rather than a number that could be confused with a version.

Solutions

  1. Change verified_at to an RFC3339 string like `"2026-07-05T00:00:00Z"` (quote it to prevent YAML type coercion)
  2. If you intended an epoch timestamp, convert it to an ISO-8601 UTC string first
  3. Check for accidental YAML structure — ensure verified_at maps to a single scalar, not a nested list/mapping

Example fix

# before
verified_at: 20260705

# after
verified_at: "2026-07-05T00:00:00Z"
Defensive patterns

Strategy: type-guard

Validate before calling

def verified_at_type_ok(row: dict) -> bool:
    v = row.get("verified_at")
    return isinstance(v, (str, datetime))

Type guard

def is_timestamp_like(value: object) -> bool:
    return isinstance(value, str) or isinstance(value, datetime)

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "RFC3339 timestamp" in str(e):
        coerce_to_iso_string(str(e))

Prevention

When it happens

Trigger: A row sets verified_at to a number (e.g. `verified_at: 20260705`), a boolean, a list, or a YAML date written in a form PyYAML parses as non-datetime non-string. Called from validate_row/validate_catalog via parsed_time.

Common situations: Writing a date as digits without quotes so YAML makes it an int; a script emitting an epoch/unix timestamp; accidentally quoting the wrong field so a list ends up in verified_at.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/3abf45d121cd57f9. Report an issue: GitHub.

Appendix: source

Thrown at shared/provider-catalog/validate_catalog.py:110

    except (OSError, yaml.YAMLError) as error:
        raise CatalogError(f"{path}: invalid YAML: {error}") from error
    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")

View on GitHub (pinned to 3ee70a1026)