{"record":{"id":"e4bca52c2dea3f9a","repo":"affaan-m/ECC","slug":"artifact-relative-has-an-unbound-source-duration","errorCode":null,"errorMessage":"artifact {relative} has an unbound source duration","messagePattern":"artifact (.+?) has an unbound source duration","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":428,"sourceCode":"            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\n\ndef validate_bundle(out_dir: str | Path) -> None:\n    \"\"\"Validate required multimodal files and cross-artifact invariants.\"\"\"\n    out_dir = Path(out_dir)","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L410-L446","documentation":"For a media_seconds source, the receipt must record 'source_duration' equal to the known duration of the cited reference file, as computed from the bundle's source_durations map. This error is raised when the expected duration is unknown for that (path, digest) pair or the recorded source_duration differs. It binds the artifact's time references to the real length of the source media.","triggerScenarios":"A media_seconds provenance source whose source_duration is missing, stale after the reference media was re-encoded to a different length, or the reference is not in source_durations (e.g. not a recognized media file).","commonSituations":"Trimming or re-encoding a reference video without regenerating the receipt, copying receipts across bundles where the duration table differs, typos in source_duration values, referencing a non-media file.","solutions":["Update source_duration to the actual duration of the referenced media file (probe with ffprobe or the library's duration helper)","Re-register the reference media so it appears in source_durations, then validate again","If the reference media changed length, regenerate the artifact and receipt together","Remove media_seconds provenance pointing at non-media references"],"exampleFix":"# before\n{'time_basis': 'media_seconds', 'reference_times': [12.5], 'source_duration': 30.0}  # stale\n# after: recompute from actual media\n{'time_basis': 'media_seconds', 'reference_times': [12.5], 'source_duration': 41.7}","handlingStrategy":"validation","validationCode":"expected = source_durations.get((src['reference_path'], src['reference_sha256']))\nassert expected is not None and src.get('source_duration') == expected, 'source_duration unbound'","typeGuard":"def duration_is_bound(src: dict, source_durations: dict) -> bool:\n    expected = source_durations.get((src.get('reference_path'), src.get('reference_sha256')))\n    return expected is not None and src.get('source_duration') == expected","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir)\nexcept ContractError as e:\n    if 'unbound source duration' in str(e):\n        refresh_source_durations(receipt, out_dir)\n    else:\n        raise","preventionTips":["Probe media durations with ffprobe whenever reference media is re-encoded","Regenerate receipts after any trim/re-encode of reference media","Only declare media_seconds provenance for recognized media references","Keep the duration table generated from the same reference files as known_sources"],"tags":["provenance","media-duration","validation"],"backgroundTag":"value-out-of-range","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"}