{"record":{"id":"de66e36b681ca931","repo":"affaan-m/ECC","slug":"effect-event-get-effect-lacks-placement-constraints","errorCode":null,"errorMessage":"effect {event.get('effect')} lacks placement constraints","messagePattern":"effect (.+?) lacks placement constraints","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":255,"sourceCode":"            if not isinstance(anchor, dict) or not required.issubset(anchor):\n                raise ContractError(f\"CV effect {event.get('effect')} lacks a valid subject anchor\")\n            if anchor.get(\"mode\") not in {\"object_track\", \"point_track\", \"segmentation_track\"}:\n                raise ContractError(f\"CV effect {event.get('effect')} has an invalid subject anchor\")\n            if anchor.get(\"lost_policy\") != \"disable_effect_until_track_recovers\":\n                raise ContractError(f\"CV effect {event.get('effect')} must fail closed on anchor loss\")\n            _validate_media_time(\n                anchor.get(\"evidence_time\"), anchor.get(\"source_duration\"),\n                label=\"anchor evidence time\",\n            )\n            if reference_durations is not None:\n                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\")","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L237-L273","documentation":"ContractError (f-string) raised when an effect event's 'placement' is missing, not a dict, or lacks any of the required keys: safe_area, max_coverage, occlusion_policy. The contract requires every effect event to declare placement constraints so generated overlays respect composition and occlusion rules.","triggerScenarios":"Calling validate_effect_recipe with an event lacking the placement key entirely; placement set to null, a list, or a string; a placement dict missing one of safe_area, max_coverage, or occlusion_policy (e.g. only {\"safe_area\": {...}}).","commonSituations":"Minimal hand-authored events that skip placement; generators that emit placement only for overlay-type effects; key typos like maxCoverage or occlusionPolicy; merging event dicts that dropped the placement subtree.","solutions":["Add a complete placement dict to every event: {\"safe_area\": ..., \"max_coverage\": ..., \"occlusion_policy\": ...}.","Copy a known-good placement block from an existing valid recipe and adjust values.","Extend your generator so it always emits all three placement keys with sensible defaults.","Catch ContractError and report which of the three keys is missing from the event named in the message."],"exampleFix":"// before\n{\"time\": 0.5, \"effect\": \"logo_overlay\"}\n// after\n{\"time\": 0.5, \"effect\": \"logo_overlay\", \"placement\": {\"safe_area\": \"center_80pct\", \"max_coverage\": 0.15, \"occlusion_policy\": \"behind_subject\"}}","handlingStrategy":"validation","validationCode":"PLACEMENT_KEYS = {\"safe_area\", \"max_coverage\", \"occlusion_policy\"}\nfor event in recipe[\"events\"]:\n    placement = event.get(\"placement\")\n    if not isinstance(placement, dict) or not PLACEMENT_KEYS.issubset(placement):\n        raise ValueError(f\"{event.get('effect')}: placement constraints incomplete\")","typeGuard":"def has_complete_placement(event) -> bool:\n    required = {\"safe_area\", \"max_coverage\", \"occlusion_policy\"}\n    return isinstance(event.get(\"placement\"), dict) and required.issubset(event[\"placement\"])","tryCatchPattern":"try:\n    validate_effect_recipe(recipe, reference_durations)\nexcept ContractError as exc:\n    if \"lacks placement constraints\" in str(exc):\n        event[\"placement\"] = default_placement()\n        validate_effect_recipe(recipe, reference_durations)\n    else:\n        raise","preventionTips":["Give every event a placement dict via a defaulting constructor","Use snake_case keys exactly: safe_area, max_coverage, occlusion_policy","Snapshot-test generated events for complete placement blocks","Reject partially filled placement dicts at authoring time"],"tags":["validation","contract","missing-field"],"backgroundTag":"schema-validation-failed","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"}