{"record":{"id":"034407b8c91229a3","repo":"affaan-m/ECC","slug":"unbound-emitted-artifact-sorted-missing","errorCode":null,"errorMessage":"unbound emitted artifact: {sorted(missing)}","messagePattern":"unbound emitted artifact: (.+?)","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":382,"sourceCode":"            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):\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)):","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L364-L400","documentation":"validate_artifact_receipt walks the output directory and compares the set of emitted files (everything except receipt.json) against the paths bound in the receipt. This ContractError is raised when a file exists on disk under out_dir but no receipt entry references it — the bundle contains an artifact the receipt cannot vouch for.","triggerScenarios":"Calling validate_artifact_receipt()/validate_bundle() after new files were added to out_dir post-generation (stray outputs, editor temp files, .DS_Store, partial re-runs) or after deleting an entry from receipt.json without deleting the file.","commonSituations":"A second tool writes helper files into the output directory; a developer manually drops a file into the bundle; a re-run with different options left files from a previous run; receipt regenerated from a subset of outputs.","solutions":["Delete the stray files from the output directory, or regenerate the bundle so the receipt matches exactly.","Regenerate the receipt with tasteforge so the new file is bound.","Find the offending files with a quick diff: find out_dir -type f ! -name receipt.json and compare against the receipt paths."],"exampleFix":"# before (stray file)\nrm ./bundle/images/leftover.png && validate\n# after (regenerate so receipt binds it)\ntasteforge generate ./bundle && validate","handlingStrategy":"try-catch","validationCode":"emitted = {p.relative_to(out_dir).as_posix() for p in out_dir.rglob('*') if p.is_file() and p.name != 'receipt.json'}\nbound = {e['path'] for e in receipt['artifacts']}\nstrays = emitted - bound\nif strays:\n    raise SystemExit(f'remove or bind stray files: {sorted(strays)}')","typeGuard":"null","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir, entries)\nexcept ContractError as e:\n    if 'unbound emitted artifact' in str(e):\n        clean_out_dir(out_dir)  # remove strays, then regenerate receipt\n    else:\n        raise","preventionTips":["Keep the output directory exclusively for tool output; no manual drop-ins.","Generate into a clean temp directory, then publish.","Configure editors/OS to not litter (e.g. .DS_Store) or add cleanup before validation."],"tags":["integrity","receipt","validation"],"backgroundTag":"checksum-mismatch","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"}