{"record":{"id":"ca9d11b83d9c7a2c","repo":"github/spec-kit","slug":"each-catalog-entry-must-be-a-mapping","errorCode":null,"errorMessage":"Each catalog entry must be a mapping.","messagePattern":"Each catalog entry must be a mapping\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/catalog.py","lineNumber":159,"sourceCode":"    role: str\n    description: str\n    author: str\n    license: str\n    download_url: str\n    requires_speckit_version: str\n    sha256: str | None = None\n    provides: dict[str, int] = field(default_factory=dict)\n    repository: str | None = None\n    tags: tuple[str, ...] = ()\n    verified: bool = False\n    # Resolution provenance (filled in by the catalog stack at lookup time):\n    source_id: str | None = None\n    source_policy: InstallPolicy | None = None\n\n    @classmethod\n    def from_dict(cls, data: Any) -> \"CatalogEntry\":\n        if not isinstance(data, dict):\n            raise BundlerError(\"Each catalog entry must be a mapping.\")\n        entry_id = str(data.get(\"id\", \"\")).strip()\n        # `or {}` would coerce a FALSY non-mapping (0, '', False, []) to {} before\n        # the isinstance guard, silently accepting a corrupt catalog entry; only\n        # an absent/None value means \"not present\".\n        requires = data.get(\"requires\")\n        if requires is None:\n            requires = {}\n        elif not isinstance(requires, dict):\n            raise BundlerError(\n                f\"Catalog entry '{entry_id or '<unknown>'}': 'requires' must be a \"\n                \"mapping when present.\"\n            )\n        provides_raw = data.get(\"provides\")\n        if provides_raw is None:\n            provides_raw = {}\n        elif not isinstance(provides_raw, dict):\n            raise BundlerError(\n                f\"Catalog entry '{entry_id or '<unknown>'}': 'provides' must be a \"","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/catalog.py#L141-L177","documentation":"Raised by CatalogEntry.from_dict() when an element under a catalog's 'bundles' object is not a mapping. Each value in the bundles map must itself be a JSON object describing the bundle; scalars, lists, or nulls fail this guard before any field checks run.","triggerScenarios":"A catalog payload like {\"bundles\": {\"my-bundle\": \"1.0.0\"}} where the value is a bare version string; a null value from sparse JSON; a producer that inlined a list of files as the entry.","commonSituations":"Hand-authored catalogs misunderstanding the schema; catalogs generated by scripts mapping bundle ids to version strings; JSON merge leaving null placeholders.","solutions":["Make each bundles value a full entry object with at least id (matching the key), name, version, role, and the other expected fields.","Fix the catalog generator to emit objects, not scalars.","Validate fetched catalogs with a schema check before load_catalog_payload() in test harnesses."],"exampleFix":"# before (catalog JSON)\n{\"bundles\": {\"my-bundle\": \"1.0.0\"}}\n\n# after (catalog JSON)\n{\"bundles\": {\"my-bundle\": {\"id\": \"my-bundle\", \"name\": \"My Bundle\", \"version\": \"1.0.0\", \"role\": \"command\", \"description\": \"\", \"author\": \"\", \"license\": \"\", \"download_url\": \"https://...\"}}}","handlingStrategy":"type-guard","validationCode":"for key, raw in payload.get(\"bundles\", {}).items():\n    if not isinstance(raw, dict):\n        raise ValueError(f\"bundle '{key}' entry is not an object\")","typeGuard":"def is_entry_mapping(value: object) -> bool:\n    return isinstance(value, dict)","tryCatchPattern":"try:\n    CatalogEntry.from_dict(entry_raw)\nexcept BundlerError as e:\n    if \"must be a mapping\" in str(e):\n        skip_and_report_entry(bundle_id, e)","preventionTips":["Schema-check catalogs in your test harness: every bundles value must be an object.","Generate catalogs with serializers, not string templates.","Log the offending bundle key when validation fails so authors can fix it."],"tags":["bundler","catalog","validation","untrusted-input"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}