{"record":{"id":"39354cc498b4ccc1","repo":"affaan-m/ECC","slug":"asset-taste-lineage-differs-from-its-bundle-request","errorCode":null,"errorMessage":"Asset taste lineage differs from its bundle request","messagePattern":"Asset taste lineage differs from its bundle request","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":174,"sourceCode":"            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)\n                         if 'bundle_dir' in config else (None, {}))\n    records = []\n    for asset in _assets(config.get('assets')):\n        record = {key: asset[key] for key in ('id', 'modality', 'origin')}","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L156-L192","documentation":"In _taste with verify=True (validate_assets path), the asset's stored taste fields (request_id, genre_number, genre_slug, style_fingerprint, reference_sha256) are compared against the values recorded in the bundle request. Any difference — including a mutated request_id or tampered lineage in the receipt — raises this error. The receipt's lineage must be byte-identical to what the validated bundle records.","triggerScenarios":"Calling validate_assets on a receipt whose asset record's request_id or any _TASTE_FIELDS value differs from the corresponding bundle manifest request; hand-editing a receipt's genre fields; re-validating a receipt against a different (newer) bundle than the one used at ingest time.","commonSituations":"Manual receipt edits (fixing fields by hand); receipts copied across projects and re-validated against a different bundle; bundle regenerated so request contents changed while the old receipt was kept; accidental asset-record mutation during post-processing scripts.","solutions":["Re-run ingest_assets against the correct bundle to produce a fresh receipt instead of editing the old one.","Diff the receipt asset fields against <bundle_dir>/manifests/<modality>.json requests to find the differing field.","If the bundle changed since ingest, restore the original bundle (validated by its receipt fingerprint) or re-ingest with the new bundle.","Do not mutate request_id on a receipt record; it must equal the bundle request's id."],"exampleFix":"# before: hand-edited receipt asset\n{\"request_id\": \"req-001\", \"genre_slug\": \"cyberpunk-noir\", ...}\n# after: regenerate the receipt from the bundle\nreceipt = ingest_assets('ingest.config.json', 'receipt.new.json')","handlingStrategy":"validation","validationCode":"def lineage_matches(asset, request):\n    fields = ('request_id','genre_number','genre_slug','style_fingerprint','reference_sha256')\n    return all(asset.get(k) == request.get(k) for k in fields)\n# check each receipt asset against its bundle manifest request before validate_assets","typeGuard":null,"tryCatchPattern":"try:\n    validate_assets('receipt.json')\nexcept ValueError as e:\n    if 'taste lineage differs' in str(e):\n        reingest_fresh_receipt()\n    else:\n        raise","preventionTips":["Never hand-edit receipts; treat them as immutable artifacts.","Keep the exact bundle used at ingest alongside the receipt (its fingerprint is in bundle_receipt).","Re-ingest after any bundle change instead of patching receipts.","Store receipts read-only in VCS."],"tags":["validation","tampering","lineage","receipt"],"backgroundTag":"checksum-mismatch","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"}