affaan-m/ECC · error · ContractError
artifact has invalid modality binding
Error message
artifact {relative} has invalid modality binding What it means
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.
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.
Example fix
// before
{"modalities": ["Image", "text "]}
// after
{"modalities": ["image", "text"]} Defensive patterns
Strategy: validation
Validate before calling
ALLOWED = {'image', 'text', 'audio', 'video'} # mirror _REQUIRED_MODALITIES
for i, e in enumerate(receipt['artifacts']):
m = e.get('modalities')
if not isinstance(m, list) or any(x not in ALLOWED for x in m):
raise SystemExit(f'artifact[{i}] has invalid modalities: {m}') Type guard
def has_valid_modalities(entry: dict, allowed: set[str]) -> bool:
m = entry.get('modalities')
return isinstance(m, list) and all(isinstance(x, str) and x in allowed for x in m) Try / catch
try:
validate_artifact_receipt(out_dir, entries)
except ContractError as e:
if 'invalid modality binding' in str(e):
regenerate_receipt(receipt_path) # enum names come from the generator
else:
raise Prevention
- 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.
When it happens
Trigger: 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).
Common situations: Typos like "image " or "Image" vs the canonical lowercase enum; a tool version that renamed modalities; hand-authored receipt entries guessing at modality names.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- artifact path must be a non-empty relative path
- artifact byte size does not match receipt
- artifact has invalid reference path
- artifact lacks exact reference/time provenance
- artifact lacks genre binding
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/f3bc28e148d57598.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:401
if extra:
raise ContractError(f"receipt binds missing artifact: {sorted(extra)}")
for entry in entries:
relative = entry.get("path")
assert isinstance(relative, str)
path = (out_dir / relative).resolve()
try:
path.relative_to(out_dir)
except ValueError as error:
raise ContractError(f"artifact path escapes output directory: {relative}") from error
if entry.get("provider_execution") is not False:
raise ContractError(f"artifact {relative} permits provider execution")
if not isinstance(entry.get("genre_numbers"), list):
raise ContractError(f"artifact {relative} lacks genre binding")
modalities = entry.get("modalities")
if (not isinstance(modalities, list)
or any(modality not in _REQUIRED_MODALITIES for modality in modalities)):
raise ContractError(f"artifact {relative} has invalid modality binding")
if entry.get("bytes") != path.stat().st_size:
raise ContractError(f"artifact {relative} byte size does not match receipt")
if entry.get("sha256") != _sha256(path):
raise ContractError(f"artifact {relative} SHA-256 does not match receipt")
provenance = entry.get("provenance")
if not isinstance(provenance, list) or not provenance:
raise ContractError(f"artifact {relative} lacks exact reference/time provenance")
for source in provenance:
if not isinstance(source.get("reference_path"), str) or not source["reference_path"]:
raise ContractError(f"artifact {relative} has invalid reference path")
digest = source.get("reference_sha256")
if not isinstance(digest, str) or len(digest) != 64:
raise ContractError(f"artifact {relative} has invalid reference SHA-256")
if (source["reference_path"], digest) not in known_sources:
raise ContractError(f"artifact {relative} cites an unknown provenance source")
times = source.get("reference_times")
basis = source.get("time_basis")
if not isinstance(times, list) or basis not in {"media_seconds", "whole_file"}:View on GitHub (pinned to 8321021c54)