affaan-m/ECC · critical · ContractError

receipt crosses the dry-run boundary

Error message

receipt crosses the dry-run boundary

What it means

validate_bundle enforces that the receipt proves a pure dry run: dry_run must be exactly True, provider_execution exactly False, and provider_calls exactly the integer 0. If any of these invariants is broken, the receipt indicates a provider was invoked, which this package forbids, so the ContractError is raised. This is the fail-closed boundary that keeps offline packages from performing network calls.

Solutions

  1. Regenerate the bundle and receipt via a normal dry-run invocation of tasteforge
  2. Restore an original, unedited receipt.json — do not hand-edit dry_run/provider fields
  3. Ensure provider_calls is the JSON integer 0 (not "0" or 0.0) and dry_run/provider_execution are JSON booleans
  4. Verify the receipt belongs to this package's bundle, not another tool's output

Example fix

// before (edited receipt)
{"dry_run": "true", "provider_execution": false, "provider_calls": "0"}
// after
{"dry_run": true, "provider_execution": false, "provider_calls": 0}
Defensive patterns

Strategy: validation

Validate before calling

import json, pathlib
def receipt_is_dry_run(out_dir) -> bool:
    r = json.loads((pathlib.Path(out_dir) / "receipt.json").read_text())
    return (r.get("dry_run") is True
            and r.get("provider_execution") is False
            and type(r.get("provider_calls")) is int
            and r.get("provider_calls") == 0)

Type guard

def is_dry_run_receipt(r) -> bool:
    return isinstance(r, dict) and r.get("dry_run") is True and r.get("provider_execution") is False and type(r.get("provider_calls")) is int and r["provider_calls"] == 0

Try / catch

from tasteforge.contract import ContractError
try:
    validate_bundle(out_dir)
except ContractError as e:
    if "dry-run boundary" in str(e):
        raise SystemExit("Receipt indicates provider execution; regenerate via dry-run only")

Prevention

When it happens

Trigger: Receipt JSON with dry_run != true, provider_execution != false, provider_calls missing/non-int, or provider_calls != 0 — e.g. a hand-edited receipt, a receipt from a future/pro variant with live providers, or tampering attempts like using 0.0, "0", or false.

Common situations: Hand-editing receipts to fake values, mixing receipts between packages that do execute providers and this dry-run-only package, or type confusion in JSON (string/float zero instead of int 0, which fails the strict `type(...) is not int` check).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — 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/ee441ff1029e58c8. Report an issue: GitHub.

Appendix: source

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

    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")
        reference_durations[digest] = duration

    recipe_path = out_dir / "resolve" / "effect_recipe.json"
    if not recipe_path.is_file():
        raise ContractError("missing Resolve effect recipe")
    validate_effect_recipe(

View on GitHub (pinned to 8321021c54)