{"record":{"id":"14718f6b3f7b3f77","repo":"affaan-m/ECC","slug":"each-asset-must-be-an-object","errorCode":null,"errorMessage":"Each asset must be an object","messagePattern":"Each asset must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":94,"sourceCode":"\ndef _load(path: Path) -> dict[str, Any]:\n    _fingerprint(path)\n    try:\n        data = json.loads(path.read_text(encoding='utf-8'))\n    except (ValueError, OSError) as exc:\n        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):","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L76-L112","documentation":"Item-type guard in _assets: the assets value is a list, but one of its entries is not a JSON object. Each asset must be a dict with its own fields; the offending element is a scalar, string, or array.","triggerScenarios":"An assets list containing bare strings (e.g., [\"a.png\"]) or mixed types, passed to ingest_assets or validate_assets.","commonSituations":"Writing paths as strings instead of asset objects; YAML converted to JSON producing scalars; a simplification attempt where users list filenames directly.","solutions":["Replace each string entry with an object containing at least id, modality, origin, and path fields.","Match the schema used by existing working manifests in the project.","Check any converter (YAML->JSON) output for scalar entries."],"exampleFix":"// before\n\"assets\": [\"hero.png\", \"logo.png\"]\n\n// after\n\"assets\": [{\"id\": \"hero\", \"modality\": \"image\", \"origin\": \"local\", \"path\": \"hero.png\"}]","handlingStrategy":"type-guard","validationCode":"import json\ndata = json.load(open(path))\nassert all(isinstance(a, dict) for a in data['assets']), 'every asset must be an object'","typeGuard":"def all_asset_objects(assets) -> bool:\n    return isinstance(assets, list) and all(isinstance(a, dict) for a in assets)","tryCatchPattern":"try:\n    validate_assets(path)\nexcept ValueError as e:\n    if str(e) == 'Each asset must be an object':\n        convert_string_paths_to_asset_objects(path)","preventionTips":["Use typed manifest builders instead of hand-written JSON","Validate against a JSON Schema before ingest","Check YAML-to-JSON converters preserve object structure"],"tags":["validation","schema","manifest"],"backgroundTag":"schema-validation-failed","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"}