affaan-m/ECC · error · ContractError
unbound emitted artifact
Error message
unbound emitted artifact: {sorted(missing)} What it means
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.
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.
Example fix
# before (stray file) rm ./bundle/images/leftover.png && validate # after (regenerate so receipt binds it) tasteforge generate ./bundle && validate
Defensive patterns
Strategy: try-catch
Validate before calling
emitted = {p.relative_to(out_dir).as_posix() for p in out_dir.rglob('*') if p.is_file() and p.name != 'receipt.json'}
bound = {e['path'] for e in receipt['artifacts']}
strays = emitted - bound
if strays:
raise SystemExit(f'remove or bind stray files: {sorted(strays)}') Type guard
null
Try / catch
try:
validate_artifact_receipt(out_dir, entries)
except ContractError as e:
if 'unbound emitted artifact' in str(e):
clean_out_dir(out_dir) # remove strays, then regenerate receipt
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- artifact byte size does not match receipt
- Local ingestion must have zero provider calls
- receipt binds missing artifact
- application bundle differs from its bound evidence
- approval evidence must bind exact source, candidate and…
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/034407b8c91229a3.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:382
raise ContractError(f"receipt source SHA-256 changed after generation: {source_path}")
emitted = {
path.relative_to(out_dir).as_posix()
for path in out_dir.rglob("*")
if path.is_file() and path.name != "receipt.json"
}
bound_paths: list[str] = []
for entry in entries:
relative = entry.get("path")
if not isinstance(relative, str) or not relative:
raise ContractError("artifact path must be a non-empty relative path")
bound_paths.append(relative)
if len(bound_paths) != len(set(bound_paths)):
raise ContractError("receipt contains duplicate artifact paths")
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)):View on GitHub (pinned to 8321021c54)