{"record":{"id":"3cc4e5ab33a08f20","repo":"affaan-m/ECC","slug":"receipt-contains-duplicate-artifact-paths","errorCode":null,"errorMessage":"receipt contains duplicate artifact paths","messagePattern":"receipt contains duplicate artifact paths","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":378,"sourceCode":"            continue\n        except OSError:\n            raise ContractError(f\"receipt source cannot be securely read: {source_path}\") from None\n        if actual_digest != expected_digest:\n            raise ContractError(f\"receipt source SHA-256 changed after generation: {source_path}\")\n\n    emitted = {\n        path.relative_to(out_dir).as_posix()\n        for path in out_dir.rglob(\"*\")\n        if path.is_file() and path.name != \"receipt.json\"\n    }\n    bound_paths: list[str] = []\n    for entry in entries:\n        relative = entry.get(\"path\")\n        if not isinstance(relative, str) or not relative:\n            raise ContractError(\"artifact path must be a non-empty relative path\")\n        bound_paths.append(relative)\n    if len(bound_paths) != len(set(bound_paths)):\n        raise ContractError(\"receipt contains duplicate artifact paths\")\n    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):","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L360-L396","documentation":"The receipt must bind each emitted artifact exactly once. This ContractError is raised when two or more entries in the artifacts array share the same 'path' string, detected by comparing the list length against the set of unique paths. Duplicates indicate a corrupted or hand-crafted receipt.","triggerScenarios":"Calling validate_artifact_receipt()/validate_bundle() on a receipt.json where artifacts[] contains the same path value more than once (e.g. duplicated entries from a merge or scripted edit).","commonSituations":"Merging two receipts or re-running a generation script that appends instead of replaces; manual copy-paste of an entry; a buggy custom post-processor that duplicates artifacts.","solutions":["Remove the duplicate artifact entries so each path appears once.","Regenerate the receipt with tasteforge to produce a canonical artifacts list.","Deduplicate programmatically before validation, e.g. jq sorting artifacts by .path and dropping repeats."],"exampleFix":"// before\n{\"artifacts\": [{\"path\": \"a.png\", ...}, {\"path\": \"a.png\", ...}]}\n// after\n{\"artifacts\": [{\"path\": \"a.png\", ...}]}","handlingStrategy":"validation","validationCode":"paths = [e.get('path') for e in receipt['artifacts']]\nif len(paths) != len(set(paths)):\n    dupes = sorted({p for p in paths if paths.count(p) > 1})\n    raise SystemExit(f'duplicate receipt paths: {dupes}')","typeGuard":"def paths_are_unique(entries: list[dict]) -> bool:\n    paths = [e.get('path') for e in entries if isinstance(e.get('path'), str)]\n    return len(paths) == len(set(paths))","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir, entries)\nexcept ContractError as e:\n    if 'duplicate artifact paths' in str(e):\n        dedupe_and_rewrite_receipt(receipt_path)\n    else:\n        raise","preventionTips":["Treat receipts as generated artifacts — always regenerate, never merge by hand.","Deduplicate artifacts in any post-processing script before writing the receipt.","Add a uniqueness lint for receipt paths in your build pipeline."],"tags":["validation","receipt","schema"],"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"}