affaan-m/ECC · error · ContractError
missing provenance
Error message
missing provenance
What it means
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.
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
Example fix
# before validate_bundle(out_dir / "genres") # after validate_bundle(out_dir) # bundle root, contains provenance.json
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def ensure_bundle_complete(out_dir: Path) -> None:
if not (out_dir / "provenance.json").is_file():
raise FileNotFoundError(f"{out_dir} has no provenance.json; regenerate the bundle") Type guard
def has_provenance(out_dir) -> bool:
return (out_dir / "provenance.json").is_file() Try / catch
from tasteforge.contract import ContractError
try:
validate_bundle(out_dir)
except ContractError as e:
if "missing provenance" in str(e):
regenerate_bundle(out_dir) # re-run pipeline to restore artifacts Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
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
- missing receipt
- missing Resolve effect recipe
- anchor evidence source duration is not bound to its receipt…
- Asset request_id/modality does not match the bundle
- cannot read overlay image
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/47240e81e556b0c6.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:455
actual_digest = hashlib.sha256(
json.dumps(digest_payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
if claimed_digest != actual_digest:
raise ContractError("receipt SHA-256 does not match its canonical content")
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):View on GitHub (pinned to 8321021c54)