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

  1. Delete the stray files from the output directory, or regenerate the bundle so the receipt matches exactly.
  2. Regenerate the receipt with tasteforge so the new file is bound.
  3. 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

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


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)