{"record":{"id":"47240e81e556b0c6","repo":"affaan-m/ECC","slug":"missing-provenance","errorCode":null,"errorMessage":"missing provenance","messagePattern":"missing provenance","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":455,"sourceCode":"    actual_digest = hashlib.sha256(\n        json.dumps(digest_payload, sort_keys=True, separators=(\",\", \":\")).encode(\"utf-8\")\n    ).hexdigest()\n    if claimed_digest != actual_digest:\n        raise ContractError(\"receipt SHA-256 does not match its canonical content\")\n\n\ndef validate_bundle(out_dir: str | Path) -> None:\n    \"\"\"Validate required multimodal files and cross-artifact invariants.\"\"\"\n    out_dir = Path(out_dir)\n    _validate_output_tree(out_dir)\n    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):","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L437-L473","documentation":"validate_bundle checks that every tasteforge output bundle contains a provenance.json file documenting how the bundle was produced. If the file is absent from the bundle's out_dir, the contract layer raises this ContractError. Provenance is a mandatory bundle artifact, so the validator fails fast rather than accepting an incomplete bundle.","triggerScenarios":"Calling validate_bundle (directly or via _bundle) on an output directory that lacks out_dir/provenance.json — e.g. a partially written run, a manually pruned directory, or a generator that skipped provenance emission.","commonSituations":"Running an older/partial pipeline that predates provenance emission, deleting provenance.json while copying bundles, or pointing validate_bundle at the wrong directory (e.g. the genres/ subdir instead of the bundle root).","solutions":["Re-run the full tasteforge pipeline so the bundle is regenerated with provenance.json present","Check the bundle path passed to validate_bundle — it must be the bundle root, not a subdirectory","If copying/archiving bundles, copy all top-level JSON files including provenance.json","If writing a custom generator, emit provenance.json into out_dir before validation"],"exampleFix":"# before\nvalidate_bundle(out_dir / \"genres\")\n# after\nvalidate_bundle(out_dir)  # bundle root, contains provenance.json","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef ensure_bundle_complete(out_dir: Path) -> None:\n    if not (out_dir / \"provenance.json\").is_file():\n        raise FileNotFoundError(f\"{out_dir} has no provenance.json; regenerate the bundle\")","typeGuard":"def has_provenance(out_dir) -> bool:\n    return (out_dir / \"provenance.json\").is_file()","tryCatchPattern":"from tasteforge.contract import ContractError\ntry:\n    validate_bundle(out_dir)\nexcept ContractError as e:\n    if \"missing provenance\" in str(e):\n        regenerate_bundle(out_dir)  # re-run pipeline to restore artifacts","preventionTips":["Always copy/move bundles as whole directories, not individual files","Validate the bundle immediately after generation to catch partial writes","Never prune 'metadata' JSON files from a bundle before validation"],"tags":["contract","file-not-found","validation","bundle"],"backgroundTag":"file-not-found","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"}