{"record":{"id":"a14a9a7057aa396f","repo":"affaan-m/ECC","slug":"artifact-relative-whole-file-provenance-must-not-invent","errorCode":null,"errorMessage":"artifact {relative} whole-file provenance must not invent times","messagePattern":"artifact (.+?) whole-file provenance must not invent times","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":424,"sourceCode":"        provenance = entry.get(\"provenance\")\n        if not isinstance(provenance, list) or not provenance:\n            raise ContractError(f\"artifact {relative} lacks exact reference/time provenance\")\n        for source in provenance:\n            if not isinstance(source.get(\"reference_path\"), str) or not source[\"reference_path\"]:\n                raise ContractError(f\"artifact {relative} has invalid reference path\")\n            digest = source.get(\"reference_sha256\")\n            if not isinstance(digest, str) or len(digest) != 64:\n                raise ContractError(f\"artifact {relative} has invalid reference SHA-256\")\n            if (source[\"reference_path\"], digest) not in known_sources:\n                raise ContractError(f\"artifact {relative} cites an unknown provenance source\")\n            times = source.get(\"reference_times\")\n            basis = source.get(\"time_basis\")\n            if not isinstance(times, list) or basis not in {\"media_seconds\", \"whole_file\"}:\n                raise ContractError(f\"artifact {relative} has invalid reference/time provenance\")\n            if basis == \"media_seconds\" and not times:\n                raise ContractError(f\"artifact {relative} lacks media reference times\")\n            if basis == \"whole_file\" and times:\n                raise ContractError(f\"artifact {relative} whole-file provenance must not invent times\")\n            if basis == \"media_seconds\":\n                expected_duration = source_durations.get((source[\"reference_path\"], digest))\n                if expected_duration is None or source.get(\"source_duration\") != expected_duration:\n                    raise ContractError(f\"artifact {relative} has an unbound source duration\")\n                for time in times:\n                    _validate_media_time(\n                        time, expected_duration,\n                        label=f\"artifact {relative} media reference time\",\n                    )\n\n    digest_payload = dict(receipt)\n    claimed_digest = digest_payload.pop(\"receipt_sha256\", None)\n    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","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L406-L442","documentation":"A whole_file provenance source means the artifact derives from the entire reference, so no per-timestamp references may be invented. This error is raised when time_basis is 'whole_file' but reference_times is a non-empty list. It prevents receipts from claiming both full-file coverage and specific time ranges.","triggerScenarios":"Setting time_basis='whole_file' while leaving reference_times populated from a previous media_seconds configuration, or generators that always emit default timestamps regardless of basis.","commonSituations":"Switching a receipt entry's basis without clearing times, copy-pasted template provenance, tools that auto-annotate times for all sources.","solutions":["Set reference_times to [] for whole_file provenance","If specific times are actually needed, change time_basis to 'media_seconds' and keep the times list","Regenerate the receipt via tasteforge so basis and times stay consistent","Remove auto-injected default timestamps from receipt post-processing scripts"],"exampleFix":"# before\n{'time_basis': 'whole_file', 'reference_times': [0.0, 45.0]}\n# after\n{'time_basis': 'whole_file', 'reference_times': []}","handlingStrategy":"validation","validationCode":"if src.get('time_basis') == 'whole_file':\n    assert src.get('reference_times') == [], 'whole_file provenance must have empty reference_times'","typeGuard":"def whole_file_times_clean(src: dict) -> bool:\n    return src.get('time_basis') != 'whole_file' or not src.get('reference_times')","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir)\nexcept ContractError as e:\n    if 'must not invent times' in str(e):\n        clear_times_for_whole_file_sources(receipt)\n    else:\n        raise","preventionTips":["Clear reference_times when switching a source to whole_file","Strip default timestamps in receipt post-processing","Use constructors that derive times emptiness from the basis automatically","Review basis changes in receipt diffs"],"tags":["provenance","time-basis","validation"],"backgroundTag":"mutually-exclusive-options","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"}