{"record":{"id":"1c0ab65ee0d6a360","repo":"affaan-m/ECC","slug":"rule-rule-get-rule-id-lacks-reference-evidence","errorCode":null,"errorMessage":"rule {rule.get('rule_id')} lacks reference evidence","messagePattern":"rule (.+?) lacks reference evidence","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":266,"sourceCode":"                digest = anchor.get(\"source_ref_sha256\")\n                expected_duration = reference_durations.get(digest) if isinstance(digest, str) else None\n                if expected_duration is None or anchor.get(\"source_duration\") != expected_duration:\n                    raise ContractError(\"anchor evidence source duration is not bound to its receipt reference\")\n    for event in events:\n        placement = event.get(\"placement\")\n        if not isinstance(placement, dict) or not {\"safe_area\", \"max_coverage\", \"occlusion_policy\"}.issubset(placement):\n            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()","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L248-L284","documentation":"Each provenance rule must cite at least one piece of reference evidence. When a rule's 'evidence' key is missing, not a list, or an empty list, this ContractError is raised so every derived rule is traceable to its source media.","triggerScenarios":"validate_provenance encounters a rule dict like {'rule_id': 'r1'} with no 'evidence' key, {'rule_id': 'r1', 'evidence': None}, or {'rule_id': 'r1', 'evidence': []}.","commonSituations":"Producers that build rule objects but never attach evidence entries; a rule template copied without its evidence field; evidence stripped by a downstream transform that drops keys; rules authored manually in a hand-edited provenance file.","solutions":["Add a non-empty 'evidence' list to each rule object in the provenance payload.","Check the rule-generation code path that creates rule dicts and ensure it appends evidence items before validation.","Regenerate provenance with the official Tasteforge producer instead of hand-editing rule entries.","Log the rule_id reported in the error to locate the exact offending rule in the payload."],"exampleFix":"// before\nrule = {\"rule_id\": \"r1\"}\n// after\nrule = {\"rule_id\": \"r1\", \"evidence\": [{\"reference_sha256\": \"<64-hex>\", \"times\": [0.0], \"source_duration\": 10.0}]}","handlingStrategy":"validation","validationCode":"missing = [r.get('rule_id') for r in payload.get('rules', [])\n          if not isinstance(r.get('evidence'), list) or not r['evidence']]\nif missing:\n    raise ValueError(f\"rules lacking evidence: {missing}\")","typeGuard":"def rule_has_evidence(rule: dict) -> bool:\n    ev = rule.get(\"evidence\")\n    return isinstance(ev, list) and len(ev) > 0 and all(isinstance(i, dict) for i in ev)","tryCatchPattern":"try:\n    validate_provenance(payload)\nexcept ContractError as e:\n    if \"lacks reference evidence\" in str(e):\n        rule_id = str(e).split()[-1] if e.args else \"unknown\"\n        payload = attach_evidence(payload, rule_id)\n    else:\n        raise","preventionTips":["Build rule objects with a helper that always attaches at least one evidence item.","Lint provenance JSON for required keys (rule_id, evidence) before bundling.","Avoid copying rule templates without their evidence field."],"tags":["provenance","evidence","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"}