{"record":{"id":"f3bc28e148d57598","repo":"affaan-m/ECC","slug":"artifact-relative-has-invalid-modality-binding","errorCode":null,"errorMessage":"artifact {relative} has invalid modality binding","messagePattern":"artifact (.+?) has invalid modality binding","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":401,"sourceCode":"    if extra:\n        raise ContractError(f\"receipt binds missing artifact: {sorted(extra)}\")\n\n    for entry in entries:\n        relative = entry.get(\"path\")\n        assert isinstance(relative, str)\n        path = (out_dir / relative).resolve()\n        try:\n            path.relative_to(out_dir)\n        except ValueError as error:\n            raise ContractError(f\"artifact path escapes output directory: {relative}\") from error\n        if entry.get(\"provider_execution\") is not False:\n            raise ContractError(f\"artifact {relative} permits provider execution\")\n        if not isinstance(entry.get(\"genre_numbers\"), list):\n            raise ContractError(f\"artifact {relative} lacks genre binding\")\n        modalities = entry.get(\"modalities\")\n        if (not isinstance(modalities, list)\n                or any(modality not in _REQUIRED_MODALITIES for modality in modalities)):\n            raise ContractError(f\"artifact {relative} has invalid modality binding\")\n        if entry.get(\"bytes\") != path.stat().st_size:\n            raise ContractError(f\"artifact {relative} byte size does not match receipt\")\n        if entry.get(\"sha256\") != _sha256(path):\n            raise ContractError(f\"artifact {relative} SHA-256 does not match receipt\")\n        provenance = entry.get(\"provenance\")\n        if not isinstance(provenance, list) or not provenance:\n            raise ContractError(f\"artifact {relative} lacks exact reference/time provenance\")\n        for source in provenance:\n            if not isinstance(source.get(\"reference_path\"), str) or not source[\"reference_path\"]:\n                raise ContractError(f\"artifact {relative} has invalid reference path\")\n            digest = source.get(\"reference_sha256\")\n            if not isinstance(digest, str) or len(digest) != 64:\n                raise ContractError(f\"artifact {relative} has invalid reference SHA-256\")\n            if (source[\"reference_path\"], digest) not in known_sources:\n                raise ContractError(f\"artifact {relative} cites an unknown provenance source\")\n            times = source.get(\"reference_times\")\n            basis = source.get(\"time_basis\")\n            if not isinstance(times, list) or basis not in {\"media_seconds\", \"whole_file\"}:","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L383-L419","documentation":"Each artifact's 'modalities' field must be a list whose members are all drawn from the library's required modality set (_REQUIRED_MODALITIES). This ContractError is raised when modalities is missing, not a list, or contains any unknown/unsupported modality name.","triggerScenarios":"Calling validate_artifact_receipt()/validate_bundle() with receipt entries where 'modalities' is absent, a non-list, or contains a modality string not in the allowed enum (typo, renamed modality, custom value).","commonSituations":"Typos like \"image \" or \"Image\" vs the canonical lowercase enum; a tool version that renamed modalities; hand-authored receipt entries guessing at modality names.","solutions":["Correct each artifact's modalities list to use only the exact enum values defined by the library.","Regenerate the receipt with tasteforge so modalities are written from the known set.","Inspect the allowed values (e.g. print _REQUIRED_MODALITIES in contract.py) and diff against your receipt."],"exampleFix":"// before\n{\"modalities\": [\"Image\", \"text \"]}\n// after\n{\"modalities\": [\"image\", \"text\"]}","handlingStrategy":"validation","validationCode":"ALLOWED = {'image', 'text', 'audio', 'video'}  # mirror _REQUIRED_MODALITIES\nfor i, e in enumerate(receipt['artifacts']):\n    m = e.get('modalities')\n    if not isinstance(m, list) or any(x not in ALLOWED for x in m):\n        raise SystemExit(f'artifact[{i}] has invalid modalities: {m}')","typeGuard":"def has_valid_modalities(entry: dict, allowed: set[str]) -> bool:\n    m = entry.get('modalities')\n    return isinstance(m, list) and all(isinstance(x, str) and x in allowed for x in m)","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir, entries)\nexcept ContractError as e:\n    if 'invalid modality binding' in str(e):\n        regenerate_receipt(receipt_path)  # enum names come from the generator\n    else:\n        raise","preventionTips":["Import the canonical modality set from the library instead of hardcoding names.","Regenerate receipts after upgrading the library — modality enums may change.","Avoid hand-typing modality strings; copy exact enum values."],"tags":["validation","enum","receipt"],"backgroundTag":"invalid-enum-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"}