affaan-m/ECC · error · ValueError

Taste claims require a validated bundle

Error message

Taste claims require a validated bundle

What it means

Taste metadata fields (genre_number, genre_slug, style_fingerprint, reference_sha256) and request_id are only valid when the asset resolves against a loaded, validated bundle. When no bundle requests exist, the library throws this if the asset carries any of those keys, preventing unverifiable taste claims.

Solutions

  1. Validate/ingest against the bundle root that contains manifests/ and receipt.json so the taste claims can resolve
  2. Remove request_id and taste fields from assets that are intentionally outside a bundle
  3. Regenerate the bundle first if it was never created, then re-run with it

Example fix

// before
assets = [{"id": "a1", "modality": "image", "origin": "internal", "request_id": "r1"}]
validate_assets(assets)  # no bundle
// after
assets = [{"id": "a1", "modality": "image", "origin": "internal"}]
validate_assets(assets, bundle_root="path/to/bundle")
Defensive patterns

Strategy: validation

Validate before calling

TASTE_KEYS = {"genre_number", "genre_slug", "style_fingerprint", "reference_sha256", "request_id"}
if bundle_root is None:
    for a in assets:
        bad = TASTE_KEYS & a.keys()
        if bad:
            raise ValueError(f"asset {a['id']} has bundle-only keys {bad} but no bundle")

Try / catch

try:
    ingest_assets(assets)
except ValueError as e:
    if str(e) == "Taste claims require a validated bundle":
        strip_taste_keys(assets) or point_to_bundle(); retry()
    else:
        raise

Prevention

When it happens

Trigger: Calling ingest_assets/validate_assets without a bundle directory (no manifests/receipt), while an asset contains request_id or any _TASTE_FIELDS key.

Common situations: Validating loose assets outside a bundle but keeping taste fields from an earlier export; forgetting to point the tool at the bundle root; assets exported before the bundle was generated.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/d0796bef9645b5ad. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/assets.py:165

        binding = _fingerprint(root / 'receipt.json')
    validate_bundle(root)
    requests = {}
    for modality in sorted(MODALITIES):
        manifest = _load(root / 'manifests' / f'{modality}.json')
        for request in manifest['requests']:
            key = (modality, _text(request.get('request_id'), 'request_id'))
            if key in requests:
                raise ValueError('Ambiguous duplicate bundle request')
            requests[key] = request
    if binding != _fingerprint(root / 'receipt.json'):
        raise ValueError('Bundle changed during validation')
    return binding, requests


def _taste(asset: dict[str, Any], requests: dict[Any, Any], verify: bool) -> dict[str, Any]:
    if not requests:
        if any(key in asset for key in (*_TASTE_FIELDS, 'request_id')):
            raise ValueError('Taste claims require a validated bundle')
        return {}
    request_id = _text(asset.get('request_id'), 'bundle request_id')
    request = requests.get((asset['modality'], request_id))
    if request is None:
        raise ValueError('Asset request_id/modality does not match the bundle')
    result = dict(request_id=request_id, **{key: request[key] for key in _TASTE_FIELDS})
    if verify:
        if any(asset.get(key) != value for key, value in result.items()):
            raise ValueError('Asset taste lineage differs from its bundle request')
    elif any(key in asset for key in _TASTE_FIELDS):
        raise ValueError('Taste fields are derived from the bundle, not supplied')
    return result


def ingest_assets(config_path: str | Path, out_receipt: str | Path) -> dict[str, Any]:
    """Bind local assets/provenance/lineage; write a new receipt, never overwrite."""
    config_file = _path(config_path, Path.cwd())
    config = _load(config_file)

View on GitHub (pinned to 8321021c54)