{"record":{"id":"07773b46bd0ff961","repo":"github/spec-kit","slug":"each-catalog-source-must-be-a-mapping","errorCode":null,"errorMessage":"Each catalog source must be a mapping.","messagePattern":"Each catalog source must be a mapping\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/catalog.py","lineNumber":71,"sourceCode":")\n\n\n@dataclass(frozen=True)\nclass CatalogSource:\n    id: str\n    url: str\n    priority: int\n    install_policy: InstallPolicy\n    scope: Scope = Scope.PROJECT\n\n    @property\n    def install_allowed(self) -> bool:\n        return self.install_policy is InstallPolicy.INSTALL_ALLOWED\n\n    @classmethod\n    def from_dict(cls, data: Any, scope: Scope) -> \"CatalogSource\":\n        if not isinstance(data, dict):\n            raise BundlerError(\"Each catalog source must be a mapping.\")\n        source_id = str(data.get(\"id\", \"\")).strip()\n        url = str(data.get(\"url\", \"\")).strip()\n        if not source_id:\n            raise BundlerError(\"A catalog source is missing its 'id'.\")\n        if not url:\n            raise BundlerError(f\"Catalog source '{source_id}' is missing its 'url'.\")\n        priority = data.get(\"priority\")\n        if priority is None:\n            raise BundlerError(f\"Catalog source '{source_id}' is missing its 'priority'.\")\n        if isinstance(priority, bool) or not isinstance(priority, (int, str)):\n            raise BundlerError(\n                f\"Catalog source '{source_id}' has a non-integer priority: {priority!r}.\"\n            )\n        try:\n            priority_int = int(priority)\n        except (TypeError, ValueError):\n            raise BundlerError(\n                f\"Catalog source '{source_id}' has a non-integer priority: {priority!r}.\"","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/catalog.py#L53-L89","documentation":"Raised by CatalogSource.from_dict() when a single catalog source entry in configuration is not a JSON/YAML object (dict). Sources are loaded from project and user config files whose top-level shape must be a list of mappings; any scalar, list, or null element fails this guard before field validation begins.","triggerScenarios":"A catalog config file whose 'sources'-style list contains a bare string (e.g. a url) instead of an object; a YAML dash-item that parsed as a scalar; a null entry from a trailing comma or malformed JSON merge.","commonSituations":"Hand-editing the catalog config and writing just a url per line; JSON produced by string concatenation instead of a serializer; config templating that renders an empty placeholder value.","solutions":["Make each element of the sources list an object with at least id, url, priority, and install_policy keys.","Validate the config with a JSON linter or json.load plus an isinstance(item, dict) check before relying on it.","Regenerate the config through add_source() instead of editing the file by hand."],"exampleFix":"# before (config file)\n[{\"id\": \"a\", \"url\": \"https://x/c.json\", \"priority\": 5, \"install_policy\": \"install-allowed\"}, \"https://y/c.json\"]\n\n# after\n[{\"id\": \"a\", \"url\": \"https://x/c.json\", \"priority\": 5, \"install_policy\": \"install-allowed\"},\n {\"id\": \"b\", \"url\": \"https://y/c.json\", \"priority\": 5, \"install_policy\": \"install-allowed\"}]","handlingStrategy":"type-guard","validationCode":"import json\n\ndata = json.loads(config_text)\nassert isinstance(data, list), \"catalog config must be a list\"\nfor item in data:\n    if not isinstance(item, dict):\n        raise ValueError(f\"catalog source entry is not an object: {item!r}\")","typeGuard":"def is_source_mapping(item: object) -> bool:\n    return isinstance(item, dict)","tryCatchPattern":"try:\n    CatalogSource.from_dict(item, Scope.PROJECT)\nexcept BundlerError as e:\n    log_config_error(config_path, e)  # point the user at the offending entry","preventionTips":["Generate catalog config only through add_source(), never by hand-writing JSON.","Lint config files after manual edits.","Reject non-object entries in your own config loaders before they reach this library."],"tags":["bundler","catalog","validation","configuration"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}