affaan-m/ECC · error · ContractError

artifact lacks genre binding

Error message

artifact {relative} lacks genre binding

What it means

Each artifact must be bound to a taste genre via a 'genre_numbers' field that is a list. This ContractError is raised when the field is missing, null, or not an array — the validator enforces that every emitted artifact carries an explicit genre binding for traceability.

Solutions

  1. Add a 'genre_numbers' list to each artifact entry in the receipt.
  2. Regenerate the receipt with tasteforge so genre bindings are computed and written.
  3. Check the receipt with jq '.artifacts[] | select(.genre_numbers | type != "array")' to find offending entries.

Example fix

// before
{"path": "a.png", "provider_execution": false}
// after
{"path": "a.png", "provider_execution": false, "genre_numbers": [3, 7]}
Defensive patterns

Strategy: validation

Validate before calling

for i, e in enumerate(receipt['artifacts']):
    if not isinstance(e.get('genre_numbers'), list):
        raise SystemExit(f'artifact[{i}] missing genre_numbers list')

Type guard

def has_genre_binding(entry: dict) -> bool:
    return isinstance(entry.get('genre_numbers'), list)

Try / catch

try:
    validate_artifact_receipt(out_dir, entries)
except ContractError as e:
    if 'lacks genre binding' in str(e):
        regenerate_receipt(receipt_path)  # genre bindings are computed by the generator
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_artifact_receipt()/validate_bundle() with receipt entries that omit 'genre_numbers' or set it to a non-list value (string, number, null).

Common situations: Hand-edited receipts; custom generation scripts that skip genre assignment; receipt format from an older tool version that did not include genre bindings.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:397

    missing = emitted - set(bound_paths)
    extra = set(bound_paths) - emitted
    if missing:
        raise ContractError(f"unbound emitted artifact: {sorted(missing)}")
    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:

View on GitHub (pinned to 8321021c54)