affaan-m/ECC · error · ContractError

receipt binds missing artifact

Error message

receipt binds missing artifact: {sorted(extra)}

What it means

This is the inverse of the unbound-artifact check: every path listed in receipt.json must exist as an actual file under the output directory. This ContractError is raised when the receipt references a path that is not present among the emitted files — the receipt claims an artifact that does not exist.

Solutions

  1. Regenerate the bundle and receipt together so the receipt only binds files that exist.
  2. Restore the missing file from version control or backup.
  3. Remove the stale entry from receipt.json if the artifact was intentionally dropped, then re-validate.

Example fix

// before (receipt lists missing file)
{"artifacts": [{"path": "gone.png", ...}]}
// after (regenerate receipt after deletion)
tasteforge generate ./bundle  # receipt now omits gone.png
Defensive patterns

Strategy: validation

Validate before calling

bound = {e['path'] for e in receipt['artifacts']}
missing = bound - emitted
if missing:
    raise SystemExit(f'missing artifact files: {sorted(missing)}')

Type guard

def all_artifacts_exist(out_dir: Path, entries: list[dict]) -> bool:
    return all((out_dir / e['path']).is_file() for e in entries)

Try / catch

try:
    validate_artifact_receipt(out_dir, entries)
except ContractError as e:
    if 'binds missing artifact' in str(e):
        restore_or_regenerate(out_dir)  # git restore files or rerun generation
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_artifact_receipt()/validate_bundle() after an artifact file was deleted or renamed post-generation, the receipt was hand-edited to add a path, or generation silently skipped an output the receipt still lists.

Common situations: A cleanup script removed output files but kept the receipt; a file was renamed without regenerating; partial transfer of the bundle (file lost in copy); receipt from a different/older run mixed with a new output directory.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/b01a39da6b4fba5b. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:384

    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)):
            raise ContractError(f"artifact {relative} has invalid modality binding")
        if entry.get("bytes") != path.stat().st_size:

View on GitHub (pinned to 8321021c54)