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
- Regenerate the bundle and receipt together so the receipt only binds files that exist.
- Restore the missing file from version control or backup.
- 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
- Regenerate the receipt whenever artifacts are added, removed, or renamed.
- Transfer bundles as a single archive so files cannot be dropped in transit.
- Never mix a receipt from one run with outputs from another.
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
- artifact byte size does not match receipt
- Local ingestion must have zero provider calls
- unbound emitted 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/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)