affaan-m/ECC · error · ContractError

missing receipt

Error message

missing receipt

What it means

validate_bundle requires a receipt.json in every output bundle; this receipt records dry-run status, provider execution, and provider call counts. When the file does not exist, the validator raises this ContractError. The receipt is the audit artifact proving the run stayed offline/dry-run, so its absence invalidates the bundle.

Solutions

  1. Re-run the full pipeline so receipt.json is regenerated into out_dir
  2. Verify validate_bundle receives the bundle root (which must contain receipt.json)
  3. Restore receipt.json from a backup or archive if the bundle was copied incompletely
  4. If implementing a custom generator, always write receipt.json before invoking the validator

Example fix

# before
validate_bundle(out_dir / "manifests")
# after
assert (out_dir / "receipt.json").is_file()
validate_bundle(out_dir)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
def ensure_receipt(out_dir: Path) -> None:
    if not (out_dir / "receipt.json").is_file():
        raise FileNotFoundError(f"{out_dir} has no receipt.json")

Type guard

def has_receipt(out_dir) -> bool:
    return (out_dir / "receipt.json").is_file()

Try / catch

from tasteforge.contract import ContractError
try:
    validate_bundle(out_dir)
except ContractError as e:
    if "missing receipt" in str(e):
        regenerate_bundle(out_dir)

Prevention

When it happens

Trigger: Calling validate_bundle (or _bundle) on an out_dir where receipt.json was never written or was deleted — e.g. interrupted run, wrong directory target, or a generator that omitted receipt emission.

Common situations: Copying partial bundles to another machine, running the validator against a stale checkout that predates receipt emission, or accidentally validating a subdirectory instead of the bundle root.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

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


def validate_bundle(out_dir: str | Path) -> None:
    """Validate required multimodal files and cross-artifact invariants."""
    out_dir = Path(out_dir)
    _validate_output_tree(out_dir)
    specs = [json.loads(path.read_text(encoding="utf-8"))
             for path in sorted((out_dir / "genres").glob("*.json"))]
    validate_genre_specs(specs)

    validate_manifests(out_dir / "manifests")
    provenance_path = out_dir / "provenance.json"
    if not provenance_path.is_file():
        raise ContractError("missing provenance")
    validate_provenance(json.loads(provenance_path.read_text(encoding="utf-8")))

    receipt_path = out_dir / "receipt.json"
    if not receipt_path.is_file():
        raise ContractError("missing receipt")
    receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
    if (receipt.get("dry_run") is not True
            or receipt.get("provider_execution") is not False
            or type(receipt.get("provider_calls")) is not int
            or receipt.get("provider_calls") != 0):
        raise ContractError("receipt crosses the dry-run boundary")
    validate_artifact_receipt(out_dir, receipt)

    reference_durations: dict[str, float] = {}
    for reference in receipt.get("references", []):
        digest = reference.get("sha256")
        duration = reference.get("source_duration")
        if not isinstance(digest, str) or not _is_finite_real(duration):
            raise ContractError("receipt reference cannot bind recipe evidence")
        duration = float(duration)
        previous = reference_durations.get(digest)
        if previous is not None and previous != duration:
            raise ContractError("receipt reference digest has conflicting source durations")

View on GitHub (pinned to 8321021c54)