{"record":{"id":"2e7ef9dc6f98f09e","repo":"affaan-m/ECC","slug":"cv-effect-event-get-effect-must-fail-closed-on-anchor-loss","errorCode":null,"errorMessage":"CV effect {event.get('effect')} must fail closed on anchor loss","messagePattern":"CV effect (.+?) must fail closed on anchor loss","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":242,"sourceCode":"            raise ContractError(\"stochastic schedule is periodic; repeating interval cycle\")\n    if recipe.get(\"periodic\") is not False:\n        raise ContractError(\"effect recipe must explicitly declare periodic=false\")\n    for event in events:\n        cv_effect = str(event.get(\"effect\", \"\")).startswith(\"cv_\")\n        if cv_effect and event.get(\"requires_subject_anchor\") is not True:\n            raise ContractError(f\"CV effect {event.get('effect')} must require a subject anchor\")\n        if event.get(\"requires_subject_anchor\"):\n            anchor = event.get(\"subject_anchor\")\n            required = {\n                \"mode\", \"target\", \"source_ref_sha256\", \"evidence_time\",\n                \"source_duration\", \"lost_policy\",\n            }\n            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\")","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L224-L260","documentation":"ContractError (f-string) raised when the anchor's lost_policy is not exactly 'disable_effect_until_track_recovers'. The contract mandates fail-closed behavior: when the subject track is lost, the CV effect must be disabled until the track recovers, rather than continuing or guessing.","triggerScenarios":"Calling validate_effect_recipe with an anchor whose lost_policy is e.g. \"keep_last_known\", \"continue\", \"ignore\", \"drop_event\", or missing/null.","commonSituations":"Copying anchor configs from systems with lenient loss handling; assuming any descriptive policy string is valid; older drafts of the recipe format that allowed multiple policies.","solutions":["Set lost_policy to exactly \"disable_effect_until_track_recovers\".","Remove custom loss-handling fields and rely on the contract's single supported policy.","Update recipe templates/generators to hardcode the required policy string.","Catch ContractError and log the offending policy value for the recipe author."],"exampleFix":"// before\nanchor = {..., \"lost_policy\": \"keep_last_position\"}\n// after\nanchor = {..., \"lost_policy\": \"disable_effect_until_track_recovers\"}","handlingStrategy":"validation","validationCode":"LOST_POLICY = \"disable_effect_until_track_recovers\"\nif anchor.get(\"lost_policy\") != LOST_POLICY:\n    raise ValueError(f\"lost_policy must be {LOST_POLICY!r}\")","typeGuard":"def fails_closed_on_anchor_loss(anchor) -> bool:\n    return isinstance(anchor, dict) and anchor.get(\"lost_policy\") == \"disable_effect_until_track_recovers\"","tryCatchPattern":"try:\n    validate_effect_recipe(recipe, reference_durations)\nexcept ContractError as exc:\n    if \"fail closed on anchor loss\" in str(exc):\n        anchor[\"lost_policy\"] = \"disable_effect_until_track_recovers\"\n    else:\n        raise","preventionTips":["Hardcode the single supported policy string in your anchor constructor","Never accept free-form loss policy strings from upstream systems","Template anchors with the policy prefilled","Add a contract-conformance test over all shipped recipes"],"tags":["validation","contract","policy"],"backgroundTag":"invalid-enum-value","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"}