{"record":{"id":"ee441ff1029e58c8","repo":"affaan-m/ECC","slug":"receipt-crosses-the-dry-run-boundary","errorCode":null,"errorMessage":"receipt crosses the dry-run boundary","messagePattern":"receipt crosses the dry-run boundary","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"critical","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":466,"sourceCode":"    specs = [json.loads(path.read_text(encoding=\"utf-8\"))\n             for path in sorted((out_dir / \"genres\").glob(\"*.json\"))]\n    validate_genre_specs(specs)\n\n    validate_manifests(out_dir / \"manifests\")\n    provenance_path = out_dir / \"provenance.json\"\n    if not provenance_path.is_file():\n        raise ContractError(\"missing provenance\")\n    validate_provenance(json.loads(provenance_path.read_text(encoding=\"utf-8\")))\n\n    receipt_path = out_dir / \"receipt.json\"\n    if not receipt_path.is_file():\n        raise ContractError(\"missing receipt\")\n    receipt = json.loads(receipt_path.read_text(encoding=\"utf-8\"))\n    if (receipt.get(\"dry_run\") is not True\n            or receipt.get(\"provider_execution\") is not False\n            or type(receipt.get(\"provider_calls\")) is not int\n            or receipt.get(\"provider_calls\") != 0):\n        raise ContractError(\"receipt crosses the dry-run boundary\")\n    validate_artifact_receipt(out_dir, receipt)\n\n    reference_durations: dict[str, float] = {}\n    for reference in receipt.get(\"references\", []):\n        digest = reference.get(\"sha256\")\n        duration = reference.get(\"source_duration\")\n        if not isinstance(digest, str) or not _is_finite_real(duration):\n            raise ContractError(\"receipt reference cannot bind recipe evidence\")\n        duration = float(duration)\n        previous = reference_durations.get(digest)\n        if previous is not None and previous != duration:\n            raise ContractError(\"receipt reference digest has conflicting source durations\")\n        reference_durations[digest] = duration\n\n    recipe_path = out_dir / \"resolve\" / \"effect_recipe.json\"\n    if not recipe_path.is_file():\n        raise ContractError(\"missing Resolve effect recipe\")\n    validate_effect_recipe(","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L448-L484","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Regenerate the bundle and receipt via a normal dry-run invocation of tasteforge","Restore an original, unedited receipt.json — do not hand-edit dry_run/provider fields","Ensure provider_calls is the JSON integer 0 (not \"0\" or 0.0) and dry_run/provider_execution are JSON booleans","Verify the receipt belongs to this package's bundle, not another tool's output"],"exampleFix":"// before (edited receipt)\n{\"dry_run\": \"true\", \"provider_execution\": false, \"provider_calls\": \"0\"}\n// after\n{\"dry_run\": true, \"provider_execution\": false, \"provider_calls\": 0}","handlingStrategy":"validation","validationCode":"import json, pathlib\ndef receipt_is_dry_run(out_dir) -> bool:\n    r = json.loads((pathlib.Path(out_dir) / \"receipt.json\").read_text())\n    return (r.get(\"dry_run\") is True\n            and r.get(\"provider_execution\") is False\n            and type(r.get(\"provider_calls\")) is int\n            and r.get(\"provider_calls\") == 0)","typeGuard":"def is_dry_run_receipt(r) -> bool:\n    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","tryCatchPattern":"from tasteforge.contract import ContractError\ntry:\n    validate_bundle(out_dir)\nexcept ContractError as e:\n    if \"dry-run boundary\" in str(e):\n        raise SystemExit(\"Receipt indicates provider execution; regenerate via dry-run only\")","preventionTips":["Never hand-edit receipt fields like dry_run or provider_calls","Use JSON integers and booleans (0 not \"0\"; true not \"true\") if tooling rewrites receipts","Keep dry-run-only packages separate from any provider-executing tooling"],"tags":["contract","dry-run","tampering","receipt","fail-closed"],"backgroundTag":"invalid-config-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}