{"record":{"id":"e5be519ad1b515a9","repo":"JuliusBrussee/caveman","slug":"path-every-catalog-row-must-be-an-object","errorCode":null,"errorMessage":"{path}: every catalog row must be an object","messagePattern":"(.+?): every catalog row must be an object","errorType":"validation","errorClass":"CatalogError","httpStatus":null,"severity":"error","filePath":"shared/provider-catalog/validate_catalog.py","lineNumber":97,"sourceCode":"# own pricing page; until then, the catalog is invalid.\n# MUST stay a prefix of REVIEW_MARKER_PREFIX in that script (the test suite\n# asserts it), or a renamed marker would sail past this rule.\nREVIEW_MARKER = \"# proposed-by:\"\n\n\nclass CatalogError(ValueError):\n    pass\n\n\ndef load_yaml(path: Path) -> list[dict[str, Any]]:\n    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","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/provider-catalog/validate_catalog.py#L79-L115","documentation":"The catalog YAML file parsed successfully, but at least one element of the top-level list is not a mapping (dict). The validator requires the document to be a list of row objects so each row can be key-checked; a scalar, string, or nested list element cannot represent a catalog row. Thrown by load_yaml before any per-row validation runs.","triggerScenarios":"yaml.safe_load on the catalog file returns a list containing a non-dict item, e.g. a stray `- some string` line between rows, a row indented so it merges into a scalar, or a duplicated top-level key collapsing entries. Any call path through validate_catalog -> load_yaml hits this.","commonSituations":"Hand-editing current.yaml and accidentally un-indenting a row so it becomes a bare scalar; a merge conflict resolved leaving a leftover `=======\"` fragment that parses as a list item; a script appending rows with wrong indentation; copy-pasting a row without the leading `- `.","solutions":["Open the named file and find the list item that is not a mapping — check indentation and that every row starts with `- key:` at a consistent indent level","Fix the broken row's indentation (a catalog row must be `- provider: ...` followed by sibling keys at the same deeper indent)","Remove leftover merge-conflict markers or stray scalar lines from the top-level list","Re-run the validator (or `python shared/provider-catalog/validate_catalog.py`) to confirm"],"exampleFix":"# before\n- provider: openai\n  model: gpt-4o\nstring-row-not-a-mapping\n\n# after\n- provider: openai\n  model: gpt-4o\n- provider: anthropic\n  model: claude-sonnet-4","handlingStrategy":"validation","validationCode":"import yaml\nfrom pathlib import Path\n\ndef rows_are_objects(path: str) -> bool:\n    doc = yaml.safe_load(Path(path).read_text(encoding=\"utf-8\"))\n    return isinstance(doc, list) and bool(doc) and all(isinstance(r, dict) for r in doc)","typeGuard":"def is_row_list(doc: object) -> bool:\n    return isinstance(doc, list) and all(isinstance(r, dict) for r in doc)","tryCatchPattern":"from shared.provider_catalog.validate_catalog import CatalogError\ntry:\n    validate_catalog()\nexcept CatalogError as e:\n    print(f\"catalog invalid: {e}\", file=__import__('sys').stderr)","preventionTips":["Validate current.yaml with a YAML linter/schema check before committing","Keep every row's indentation identical (consistent two-space steps)","Never hand-edit the top-level list structure; copy an existing row and modify values"],"tags":["yaml","schema-validation","python","catalog"],"backgroundTag":"schema-validation-failed","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"}