{"record":{"id":"686a00e1849648c3","repo":"affaan-m/ECC","slug":"duplicate-asset-id-asset-id","errorCode":null,"errorMessage":"Duplicate asset id: {asset_id}","messagePattern":"Duplicate asset id: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":97,"sourceCode":"    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):\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')","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L79-L115","documentation":"_assets collects each asset's validated id into a set and raises 'Duplicate asset id: {asset_id}' when the same id appears more than once. Asset ids must be unique within a manifest because they are the identity key for provenance, binding, and bundling.","triggerScenarios":"Two entries in the assets list share the same 'id' value — e.g., copy-pasted entries, or a merge of two manifests without re-keying ids.","commonSituations":"Duplicating an entry to add a second file but forgetting to change the id; programmatic manifest generation appending the same default id ('asset1') repeatedly; merging branches that each added an asset with the same id.","solutions":["Rename the duplicate entries so each id is unique (e.g., hero-v1, hero-v2).","Add a pre-flight dedupe check in any script that generates manifests.","If the same file is needed twice, reference it by the same path but give distinct ids only if the tooling supports it — otherwise reuse one entry.","Grep the manifest for repeated ids: python -c \"import json,collections;d=json.load(open('m.json'));print([i for i,c in collections.Counter(a['id'] for a in d['assets']).items() if c>1])\"."],"exampleFix":"// before\n[{\"id\": \"hero\", ...}, {\"id\": \"hero\", \"path\": \"hero-alt.png\", ...}]\n\n// after\n[{\"id\": \"hero\", ...}, {\"id\": \"hero-alt\", \"path\": \"hero-alt.png\", ...}]","handlingStrategy":"validation","validationCode":"import json, collections\ndata = json.load(open(path))\nids = [a['id'] for a in data['assets']]\ndupes = [i for i, c in collections.Counter(ids).items() if c > 1]\nassert not dupes, f'duplicate ids: {dupes}'","typeGuard":null,"tryCatchPattern":"try:\n    ingest_assets(path)\nexcept ValueError as e:\n    if str(e).startswith('Duplicate asset id'):\n        rekey_duplicates(path)","preventionTips":["Generate ids programmatically with a uniqueness guarantee (uuid or slug+counter)","Add a dedupe assert in manifest generators","When merging manifests, re-key ids from the incoming side"],"tags":["validation","uniqueness","manifest"],"backgroundTag":"invalid-identifier","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"}