{"record":{"id":"c462a888ffd9fc96","repo":"affaan-m/ECC","slug":"asset-request-id-modality-does-not-match-the-bundle","errorCode":null,"errorMessage":"Asset request_id/modality does not match the bundle","messagePattern":"Asset request_id/modality does not match the bundle","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":170,"sourceCode":"        for request in manifest['requests']:\n            key = (modality, _text(request.get('request_id'), 'request_id'))\n            if key in requests:\n                raise ValueError('Ambiguous duplicate bundle request')\n            requests[key] = request\n    if binding != _fingerprint(root / 'receipt.json'):\n        raise ValueError('Bundle changed during validation')\n    return binding, requests\n\n\ndef _taste(asset: dict[str, Any], requests: dict[Any, Any], verify: bool) -> dict[str, Any]:\n    if not requests:\n        if any(key in asset for key in (*_TASTE_FIELDS, 'request_id')):\n            raise ValueError('Taste claims require a validated bundle')\n        return {}\n    request_id = _text(asset.get('request_id'), 'bundle request_id')\n    request = requests.get((asset['modality'], request_id))\n    if request is None:\n        raise ValueError('Asset request_id/modality does not match the bundle')\n    result = dict(request_id=request_id, **{key: request[key] for key in _TASTE_FIELDS})\n    if verify:\n        if any(asset.get(key) != value for key, value in result.items()):\n            raise ValueError('Asset taste lineage differs from its bundle request')\n    elif any(key in asset for key in _TASTE_FIELDS):\n        raise ValueError('Taste fields are derived from the bundle, not supplied')\n    return result\n\n\ndef ingest_assets(config_path: str | Path, out_receipt: str | Path) -> dict[str, Any]:\n    \"\"\"Bind local assets/provenance/lineage; write a new receipt, never overwrite.\"\"\"\n    config_file = _path(config_path, Path.cwd())\n    config = _load(config_file)\n    base = config_file.parent\n    output = _path(out_receipt, Path.cwd())\n    if output.exists():\n        raise ValueError(f'Receipt output already exists: {output}')\n    binding, requests = (_bundle(config['bundle_dir'], base, False)","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L152-L188","documentation":"During _taste (called by ingest_assets and validate_assets), an asset declares a bundle request_id, but the (modality, request_id) pair is not found among the requests loaded from the validated taste bundle's manifests. The library refuses to attach taste lineage (genre_number, genre_slug, style_fingerprint, reference_sha256) to an asset whose request does not exist in the bundle for that modality. This prevents silently pairing an asset with taste fields that belong to a different request.","triggerScenarios":"Calling ingest_assets with an asset whose request_id does not exist in bundle manifests, or whose modality key (image/video/3d_asset) differs from the modality the request was filed under; calling validate_assets on a receipt containing such a mismatched (request_id, modality) combination. Also triggered by a bundle with manifests regenerated/renamed request ids after the config was written.","commonSituations":"Copy-pasting request ids between configs; regenerating a taste bundle (new random request ids) without updating asset configs; changing an asset's modality field without updating the request id; typos in request_id strings; referencing a request that lives under a different modality manifest.","solutions":["Print the bundle manifests and confirm the exact (modality, request_id) pair exists: for each modality in image/video/3d_asset, read <bundle_dir>/manifests/<modality>.json and check the requests[].request_id values.","Fix the asset's request_id in the ingest config to match a request under the same modality.","If the modality of the asset changed, move the request id lookup to the correct modality's manifest or regenerate the bundle request for that modality.","If the bundle was regenerated, update all configs to the new request ids (or re-run the bundle generation step and rewire)."],"exampleFix":"# before (config)\n{\"assets\": [{\"id\": \"a1\", \"modality\": \"video\", \"origin\": \"local_passthrough\", \"request_id\": \"req-001\"}]}\n# after (request req-001 lives under image, asset is video; use the video request id)\n{\"assets\": [{\"id\": \"a1\", \"modality\": \"video\", \"origin\": \"local_passthrough\", \"request_id\": \"req-002\"}]}","handlingStrategy":"validation","validationCode":"import json, pathlib\ndef request_in_bundle(bundle_dir, modality, request_id):\n    manifest = json.loads(pathlib.Path(bundle_dir, 'manifests', f'{modality}.json').read_text())\n    return any(r.get('request_id') == request_id for r in manifest['requests'])\n# assert request_in_bundle(cfg['bundle_dir'], asset['modality'], asset['request_id'])","typeGuard":"def has_bundle_request(asset, requests):\n    rid = asset.get('request_id')\n    return isinstance(rid, str) and (asset.get('modality'), rid) in requests","tryCatchPattern":null,"preventionTips":["Keep a manifest of (modality, request_id) pairs and validate configs against it before ingest.","Regenerate configs whenever the bundle is regenerated.","Avoid hand-typing request ids; copy them from the manifest or generate configs programmatically.","Never change an asset's modality without re-checking its request id."],"tags":["validation","bundle","lineage","config"],"backgroundTag":"record-not-found","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"}