{"record":{"id":"6f575310cf5ce73d","repo":"affaan-m/ECC","slug":"artifact-relative-lacks-genre-binding","errorCode":null,"errorMessage":"artifact {relative} lacks genre binding","messagePattern":"artifact (.+?) lacks genre binding","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":397,"sourceCode":"    missing = emitted - set(bound_paths)\n    extra = set(bound_paths) - emitted\n    if missing:\n        raise ContractError(f\"unbound emitted artifact: {sorted(missing)}\")\n    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:","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L379-L415","documentation":"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.","triggerScenarios":"Calling validate_artifact_receipt()/validate_bundle() with receipt entries that omit 'genre_numbers' or set it to a non-list value (string, number, null).","commonSituations":"Hand-edited receipts; custom generation scripts that skip genre assignment; receipt format from an older tool version that did not include genre bindings.","solutions":["Add a 'genre_numbers' list to each artifact entry in the receipt.","Regenerate the receipt with tasteforge so genre bindings are computed and written.","Check the receipt with jq '.artifacts[] | select(.genre_numbers | type != \"array\")' to find offending entries."],"exampleFix":"// before\n{\"path\": \"a.png\", \"provider_execution\": false}\n// after\n{\"path\": \"a.png\", \"provider_execution\": false, \"genre_numbers\": [3, 7]}","handlingStrategy":"validation","validationCode":"for i, e in enumerate(receipt['artifacts']):\n    if not isinstance(e.get('genre_numbers'), list):\n        raise SystemExit(f'artifact[{i}] missing genre_numbers list')","typeGuard":"def has_genre_binding(entry: dict) -> bool:\n    return isinstance(entry.get('genre_numbers'), list)","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir, entries)\nexcept ContractError as e:\n    if 'lacks genre binding' in str(e):\n        regenerate_receipt(receipt_path)  # genre bindings are computed by the generator\n    else:\n        raise","preventionTips":["Regenerate receipts rather than authoring entries by hand.","Validate receipts against a JSON schema requiring genre_numbers in CI.","When upgrading the tool, always regenerate old receipts to pick up new required fields."],"tags":["validation","receipt","metadata"],"backgroundTag":"schema-validation-failed","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"}