{"record":{"id":"ee8f65a176dee368","repo":"affaan-m/ECC","slug":"rule-rule-get-rule-id-lacks-time-evidence","errorCode":null,"errorMessage":"rule {rule.get('rule_id')} lacks time evidence","messagePattern":"rule (.+?) lacks time evidence","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":273,"sourceCode":"            raise ContractError(f\"effect {event.get('effect')} lacks placement constraints\")\n\n\ndef validate_provenance(payload: dict[str, Any]) -> None:\n    \"\"\"Require every declared rule to cite immutable, timestamped evidence.\"\"\"\n    rules = payload.get(\"rules\")\n    if not isinstance(rules, list) or not rules:\n        raise ContractError(\"provenance must contain derived rules\")\n    for rule in rules:\n        evidence = rule.get(\"evidence\")\n        if not isinstance(evidence, list) or not evidence:\n            raise ContractError(f\"rule {rule.get('rule_id')} lacks reference evidence\")\n        for item in evidence:\n            digest = item.get(\"reference_sha256\")\n            if not isinstance(digest, str) or len(digest) != 64:\n                raise ContractError(f\"rule {rule.get('rule_id')} lacks immutable reference evidence\")\n            times = item.get(\"times\")\n            if not isinstance(times, list) or not times:\n                raise ContractError(f\"rule {rule.get('rule_id')} lacks time evidence\")\n            source_duration = item.get(\"source_duration\")\n            for time in times:\n                _validate_media_time(\n                    time, source_duration, label=f\"rule {rule.get('rule_id')} time evidence\"\n                )\n\n\ndef validate_manifests(manifests_dir: str | Path) -> None:\n    \"\"\"Require image, video, and 3D-asset dry-run request manifests.\"\"\"\n    manifests_dir = Path(manifests_dir)\n    found = {path.stem for path in manifests_dir.glob(\"*.json\")} if manifests_dir.is_dir() else set()\n    missing = _REQUIRED_MODALITIES - found\n    if missing:\n        raise ContractError(f\"missing modality manifests: {sorted(missing)}\")\n    for modality in _REQUIRED_MODALITIES:\n        payload = json.loads((manifests_dir / f\"{modality}.json\").read_text(encoding=\"utf-8\"))\n        if payload.get(\"modality\") != modality or not payload.get(\"requests\"):\n            raise ContractError(f\"invalid or empty {modality} manifest\")","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L255-L291","documentation":"Each evidence item must carry a non-empty 'times' list of media timestamps so rules are anchored in time within the source. Missing, non-list, or empty 'times' raises this ContractError, and each time is further validated against 'source_duration'.","triggerScenarios":"Evidence item like {'reference_sha256': '<64-hex>'} with no 'times', times = None, or times = []; also times containing non-finite or out-of-duration values that fail _validate_media_time.","commonSituations":"Evidence recorded from a detector that produced no timestamps; a schema migration dropping the times field; timestamps accidentally placed at the rule level instead of per evidence item; NaN/inf times from float division by zero frame rate.","solutions":["Add a non-empty 'times' list of finite numbers to each evidence item.","Ensure each time value is finite (no NaN/inf) and within [0, source_duration].","Set a valid 'source_duration' on the evidence item if it is missing, since times are validated against it.","Fix the upstream timestamp extractor to emit at least one time per evidence item before building provenance."],"exampleFix":"// before\nitem = {\"reference_sha256\": digest, \"source_duration\": 10.0, \"times\": []}\n// after\nitem = {\"reference_sha256\": digest, \"source_duration\": 10.0, \"times\": [0.5, 2.25]}","handlingStrategy":"validation","validationCode":"import math\ndef times_ok(item):\n    times, dur = item.get(\"times\"), item.get(\"source_duration\")\n    return (isinstance(times, list) and len(times) > 0\n            and all(isinstance(t, (int, float)) and math.isfinite(t) for t in times)\n            and isinstance(dur, (int, float)) and all(0 <= t <= dur for t in times))","typeGuard":"def evidence_is_time_anchored(item: dict) -> bool:\n    import math\n    t = item.get(\"times\")\n    return isinstance(t, list) and len(t) > 0 and all(\n        isinstance(x, (int, float)) and math.isfinite(x) for x in t)","tryCatchPattern":"try:\n    validate_provenance(payload)\nexcept ContractError as e:\n    if \"lacks time evidence\" in str(e):\n        payload = reextract_timestamps(payload)\n        validate_provenance(payload)\n    else:\n        raise","preventionTips":["Require the timestamp extractor to emit at least one time per evidence item.","Clamp/validate times against source_duration at extraction time, not validation time.","Guard frame-rate divisions against zero to avoid NaN/inf times.","Keep times per evidence item, not at the rule level."],"tags":["provenance","timestamps","media","validation"],"backgroundTag":"empty-required-field","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"}