{"record":{"id":"756d340c7abe790c","repo":"JuliusBrussee/caveman","slug":"label-verified-at-must-be-rfc3339","errorCode":null,"errorMessage":"{label}: verified_at must be RFC3339","messagePattern":"(.+?): verified_at must be RFC3339","errorType":"validation","errorClass":"CatalogError","httpStatus":null,"severity":"error","filePath":"shared/provider-catalog/validate_catalog.py","lineNumber":108,"sourceCode":"    try:\n        document = yaml.safe_load(path.read_text(encoding=\"utf-8\"))\n    except (OSError, yaml.YAMLError) as error:\n        raise CatalogError(f\"{path}: invalid YAML: {error}\") from error\n    if not isinstance(document, list) or not document:\n        raise CatalogError(f\"{path}: catalog must be a non-empty list\")\n    if not all(isinstance(row, dict) for row in document):\n        raise CatalogError(f\"{path}: every catalog row must be an object\")\n    return document\n\n\ndef parsed_time(value: Any, label: str) -> datetime:\n    if isinstance(value, datetime):\n        parsed = value\n    elif isinstance(value, str):\n        try:\n            parsed = datetime.fromisoformat(value.replace(\"Z\", \"+00:00\"))\n        except ValueError as error:\n            raise CatalogError(f\"{label}: verified_at must be RFC3339\") from error\n    else:\n        raise CatalogError(f\"{label}: verified_at must be an RFC3339 timestamp\")\n    if parsed.tzinfo is None:\n        raise CatalogError(f\"{label}: verified_at must include a timezone\")\n    return parsed.astimezone(timezone.utc)\n\n\ndef validate_row(row: dict[str, Any], label: str, now: datetime) -> tuple[str, str, str]:\n    unknown = set(row) - TOP_LEVEL_KEYS\n    missing = REQUIRED_KEYS - set(row)\n    if unknown:\n        raise CatalogError(f\"{label}: unknown field(s): {', '.join(sorted(unknown))}\")\n    if missing:\n        raise CatalogError(f\"{label}: missing field(s): {', '.join(sorted(missing))}\")\n\n    identity: list[str] = []\n    for field in (\"provider\", \"model\", \"region\"):\n        value = row[field]","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/provider-catalog/validate_catalog.py#L90-L126","documentation":"verified_at was given as a string, but datetime.fromisoformat (with `Z` normalized to `+00:00`) could not parse it. The validator requires RFC3339-formatted timestamps so it can anchor each row to a dated immutable snapshot; an unparseable date proves nothing about when prices were checked. Thrown from parsed_time.","triggerScenarios":"A row's verified_at string is not ISO/RFC3339 parseable: e.g. `2026-1-5`, `July 5 2026`, `2026/07/05`, `2026-07-05 14:00` with odd formatting, or a locale-formatted date pasted into current.yaml or a dated snapshot row.","commonSituations":"Typing the date by hand instead of using ISO format; a sync script emitting a human-readable date; copying a date from a provider's pricing page in US format (MM/DD/YYYY); missing zero-padding.","solutions":["Rewrite verified_at as full RFC3339/ISO-8601 with timezone, e.g. `2026-07-05T00:00:00Z`","Zero-pad month/day/hour digits (`2026-7-5` -> `2026-07-05`)","Replace locale formats like 07/05/2026 or 'July 5, 2026' with ISO format","If the value came from a sync script, fix the script's date formatting (e.g. `datetime.now(timezone.utc).isoformat()`)"],"exampleFix":"# before\nverified_at: 7/5/2026\n\n# after\nverified_at: \"2026-07-05T00:00:00Z\"","handlingStrategy":"validation","validationCode":"from datetime import datetime\n\ndef is_rfc3339(value: str) -> bool:\n    try:\n        return datetime.fromisoformat(value.replace(\"Z\", \"+00:00\")).tzinfo is not None\n    except ValueError:\n        return False","typeGuard":"def is_timestamp(value: object) -> bool:\n    return isinstance(value, str) and is_rfc3339(value) or isinstance(value, datetime)","tryCatchPattern":"try:\n    validate_catalog()\nexcept CatalogError as e:\n    if \"verified_at must be RFC3339\" in str(e):\n        fix_date_format(str(e))","preventionTips":["Always write dates as `YYYY-MM-DDTHH:MM:SSZ`","Generate timestamps with datetime.now(timezone.utc).isoformat() in scripts","Zero-pad all date components"],"tags":["datetime","format","yaml","catalog"],"backgroundTag":"invalid-date-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}