{"record":{"id":"776d5b36cb6fd9ae","repo":"affaan-m/ECC","slug":"receipt-reference-cannot-bind-recipe-evidence","errorCode":null,"errorMessage":"receipt reference cannot bind recipe evidence","messagePattern":"receipt reference cannot bind recipe evidence","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":474,"sourceCode":"    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(\n        json.loads(recipe_path.read_text(encoding=\"utf-8\")),\n        reference_durations=reference_durations,\n    )\n","sourceCodeStart":456,"sourceCodeEnd":488,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L456-L488","documentation":"Each receipt reference must bind recipe evidence: a sha256 digest (string) and a finite numeric source_duration. If a reference lacks a valid string digest or its source_duration is missing/non-numeric/NaN/inf, the reference cannot be tied to recipe evidence and this ContractError is raised. This ensures every cited artifact has measurable, finite provenance.","triggerScenarios":"A receipt references array entry missing \"sha256\" or \"source_duration\", a null duration, a string duration like \"12.5\", or NaN/Infinity values — typically from hand-written or hand-edited receipts.","commonSituations":"Writing receipts by hand or with a custom generator, truncating JSON during copy, or emitting durations as strings from a templating step.","solutions":["Regenerate the receipt through the normal tasteforge pipeline so all references carry sha256 and numeric source_duration","Ensure each reference has sha256 as a string and source_duration as a finite number (not string/null/NaN/Infinity)","Fix custom generators to validate reference entries before writing receipt.json","Restore receipt.json from a known-good run"],"exampleFix":"// before\n{\"references\": [{\"sha256\": null, \"source_duration\": \"12.5\"}]}\n// after\n{\"references\": [{\"sha256\": \"abc123...\", \"source_duration\": 12.5}]}","handlingStrategy":"validation","validationCode":"import math, json\ndef references_valid(receipt: dict) -> bool:\n    for ref in receipt.get(\"references\", []):\n        if not isinstance(ref.get(\"sha256\"), str):\n            return False\n        d = ref.get(\"source_duration\")\n        if not isinstance(d, (int, float)) or isinstance(d, bool) or not math.isfinite(d):\n            return False\n    return True","typeGuard":"def is_valid_reference(ref) -> bool:\n    import math\n    d = ref.get(\"source_duration\")\n    return (isinstance(ref.get(\"sha256\"), str)\n            and isinstance(d, (int, float)) and not isinstance(d, bool)\n            and math.isfinite(d))","tryCatchPattern":"from tasteforge.contract import ContractError\ntry:\n    validate_bundle(out_dir)\nexcept ContractError as e:\n    if \"cannot bind recipe evidence\" in str(e):\n        fix_or_regenerate_receipt(out_dir)","preventionTips":["Generate receipts via the pipeline, never by hand","Emit durations as JSON numbers, not strings","Sanity-check receipts with a script before validation"],"tags":["contract","receipt","type-mismatch","validation"],"backgroundTag":"type-mismatch","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"}