{"record":{"id":"ccfe0835498a215e","repo":"affaan-m/ECC","slug":"taste-fields-are-derived-from-the-bundle-not-supplied","errorCode":null,"errorMessage":"Taste fields are derived from the bundle, not supplied","messagePattern":"Taste fields are derived from the bundle, not supplied","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":176,"sourceCode":"        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')}\n        record.update(_taste(asset, requests, False))\n        record.update(_fingerprint(_path(asset.get('path'), base), asset['modality']))","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L158-L194","documentation":"In _taste with verify=False (ingest_assets path), an asset config supplied one of the derived taste fields (genre_number, genre_slug, style_fingerprint, reference_sha256) even though a bundle is present. Taste fields are derived exclusively from the bundle request; supplying them in the config is rejected so the bundle remains the single source of lineage truth. Only request_id may be supplied in the config.","triggerScenarios":"Calling ingest_assets with an asset entry that includes any of genre_number/genre_slug/style_fingerprint/reference_sha256 while bundle_dir is configured; copying a validated receipt's asset record back into the ingest config (which contains the derived fields); a script merging bundle output into the config.","commonSituations":"Round-tripping a receipt back into a config by copy-paste; users 'filling in' what look like missing fields; auto-generated configs that dump full asset objects including derived lineage.","solutions":["Remove genre_number, genre_slug, style_fingerprint and reference_sha256 from the asset entry in the ingest config; keep only request_id.","If you want those values, note they will be recomputed by ingest_assets from the bundle manifest request.","Fix any config generator to emit only id, modality, origin, path, request_id (and optional provider_provenance) for bundle-bound assets."],"exampleFix":"# before\n{\"id\": \"a1\", \"modality\": \"image\", \"origin\": \"local_passthrough\", \"request_id\": \"req-001\", \"genre_slug\": \"baroque\"}\n# after\n{\"id\": \"a1\", \"modality\": \"image\", \"origin\": \"local_passthrough\", \"request_id\": \"req-001\"}","handlingStrategy":"validation","validationCode":"TASTE_FIELDS = ('genre_number','genre_slug','style_fingerprint','reference_sha256')\nassert not any(k in asset for k in TASTE_FIELDS), 'strip derived taste fields from config'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Emit only id/modality/origin/path/request_id in ingest configs.","When round-tripping receipts into configs, project only the allowed keys.","Document that taste fields are read-only derived values."],"tags":["validation","derived-fields","config","bundle"],"backgroundTag":"invalid-argument-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"}