{"record":{"id":"9b9f8de442f9504a","repo":"affaan-m/ECC","slug":"modality-must-be-image-video-or-3d-asset","errorCode":null,"errorMessage":"modality must be image, video or 3d_asset","messagePattern":"modality must be image, video or 3d_asset","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":100,"sourceCode":"        raise ValueError(f'Cannot read JSON object: {path}') from exc\n    if not isinstance(data, dict):\n        raise ValueError('Expected a JSON object')\n    return data\n\n\ndef _assets(value: Any) -> list[dict[str, Any]]:\n    if not isinstance(value, list) or not value:\n        raise ValueError('assets must be a nonempty list')\n    ids = set()\n    for asset in value:\n        if not isinstance(asset, dict):\n            raise ValueError('Each asset must be an object')\n        asset_id = _text(asset.get('id'), 'asset id')\n        if asset_id in ids:\n            raise ValueError(f'Duplicate asset id: {asset_id}')\n        ids.add(asset_id)\n        if asset.get('modality') not in tuple(MODALITIES):\n            raise ValueError('modality must be image, video or 3d_asset')\n        if asset.get('origin') not in tuple(ORIGINS):\n            raise ValueError('Invalid asset origin')\n    return value\n\n\ndef _provenance(asset: dict[str, Any], base: Path, verify: bool) -> dict[str, Any] | None:\n    source = asset.get('provider_provenance')\n    if asset['origin'] != 'external_result':\n        if source is not None:\n            raise ValueError('Provider provenance requires external_result origin')\n        return None\n    if not isinstance(source, dict):\n        raise ValueError('external_result requires provider provenance and local evidence')\n    provider = _text(source.get('provider'), 'provider')\n    identifiers = {key: _text(source[key], key) for key in ('request_id', 'workflow_id')\n                   if key in source}\n    if not identifiers:\n        raise ValueError('Provider provenance requires request_id or workflow_id')","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L82-L118","documentation":"Asset ingestion/validation requires each asset to declare a modality from the fixed set image, video, 3d_asset. The library throws this ValueError when `asset.get('modality')` is missing or not one of those exact strings, because downstream processing (bundle lookup, fingerprinting) dispatches on modality.","triggerScenarios":"Calling ingest_assets or validate_assets with an asset dict lacking a 'modality' key, or with a value like 'image2', 'Image' (case mismatch), 'mesh', or None.","commonSituations":"Typos in hand-written asset manifests; older configs using a pre-rename modality name; programmatically generated assets that omit modality; YAML/JSON key casing mistakes ('Modality').","solutions":["Set the asset's 'modality' field to exactly one of 'image', 'video', or '3d_asset'","Check for typos/case differences in the manifest and fix them","Print the offending asset dict (the error doesn't name it) to see the actual modality value","If migrating from an older manifest format, rename legacy modality terms to the current tuple"],"exampleFix":"// before\n{\"id\": \"a1\", \"modality\": \"3d\", \"origin\": \"internal\"}\n// after\n{\"id\": \"a1\", \"modality\": \"3d_asset\", \"origin\": \"internal\"}","handlingStrategy":"validation","validationCode":"ALLOWED = {\"image\", \"video\", \"3d_asset\"}\nfor a in assets:\n    if a.get(\"modality\") not in ALLOWED:\n        raise ValueError(f\"asset {a.get('id')}: modality must be one of {sorted(ALLOWED)}, got {a.get('modality')!r}\")","typeGuard":"def is_valid_modality(m) -> bool:\n    return m in (\"image\", \"video\", \"3d_asset\")","tryCatchPattern":"try:\n    ingest_assets(assets)\nexcept ValueError as e:\n    if \"modality must be\" in str(e):\n        log.error(\"asset with bad modality: %s\", find_offending_asset(assets)); fix_and_retry()\n    else:\n        raise","preventionTips":["Define a single MODALITIES constant/enum in your pipeline and always construct assets through it","Add a schema check (e.g. zod/pydantic/JSON Schema) on manifests before calling the library","Lint manifests for exact-string enum fields in CI"],"tags":["validation","assets","modality"],"backgroundTag":"invalid-enum-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}