{"record":{"id":"06ded65f7dc81352","repo":"affaan-m/ECC","slug":"effect-recipe-must-explicitly-declare-periodic-false","errorCode":null,"errorMessage":"effect recipe must explicitly declare periodic=false","messagePattern":"effect recipe must explicitly declare periodic=false","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":226,"sourceCode":"        _validate_media_time(\n            evidence.get(\"time\"), evidence.get(\"source_duration\"), label=\"effect evidence time\"\n        )\n        if reference_durations is not None:\n            digest = evidence.get(\"reference_sha256\")\n            expected_duration = reference_durations.get(digest) if isinstance(digest, str) else None\n            if expected_duration is None or evidence.get(\"source_duration\") != expected_duration:\n                raise ContractError(\"effect evidence source duration is not bound to its receipt reference\")\n    times = [float(event[\"time\"]) for event in events]\n    if times != sorted(times) or len(times) != len(set(times)):\n        raise ContractError(\"effect event times must be unique and increasing\")\n    intervals = [round(b - a, 6) for a, b in zip(times, times[1:])]  # noqa: RUF007\n    if len(set(intervals)) <= 1:\n        raise ContractError(\"stochastic schedule is periodic; intervals must vary\")\n    for period in range(1, len(intervals) // 2 + 1):\n        if all(intervals[index] == intervals[index % period] for index in range(len(intervals))):\n            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\"),","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L208-L244","documentation":"ContractError raised when an effect recipe does not explicitly set 'periodic' to False. The tasteforge contract requires recipes to affirmatively declare periodic=false, documenting that the author asserts a non-periodic schedule. Missing the key, setting it to True, or using a truthy non-boolean value all fail.","triggerScenarios":"Calling validate_effect_recipe with a recipe dict lacking the 'periodic' key; recipe contains periodic: true; recipe contains periodic: \"false\" (a string, which is not the boolean False since the check is `is not False`).","commonSituations":"Recipes written before the periodic field was introduced; hand-edited JSON omitting the declaration; YAML/JSON configs where 'false' was quoted as a string; copying a minimal recipe template that predates the requirement.","solutions":["Add \"periodic\": false to the recipe dict (boolean, not string).","Fix quoted values: change periodic: \"false\" to unquoted false in JSON/YAML.","Update recipe generation templates to always emit the periodic field.","Validate the recipe schema locally before submitting the bundle."],"exampleFix":"// before\nrecipe = {\"events\": [...], \"seed\": 42}\n// after\nrecipe = {\"events\": [...], \"seed\": 42, \"periodic\": False}","handlingStrategy":"validation","validationCode":"if recipe.get(\"periodic\") is not False:\n    raise ValueError(\"recipe must explicitly declare periodic=false (boolean)\")","typeGuard":"def declares_non_periodic(recipe) -> bool:\n    return isinstance(recipe, dict) and recipe.get(\"periodic\") is False","tryCatchPattern":"try:\n    validate_effect_recipe(recipe, reference_durations)\nexcept ContractError as exc:\n    if \"periodic=false\" in str(exc):\n        recipe[\"periodic\"] = False\n        validate_effect_recipe(recipe, reference_durations)\n    else:\n        raise","preventionTips":["Add \"periodic\": false to every recipe template","Never serialize false as the string \"false\" in JSON/YAML","Schema-validate recipes (e.g. with a JSON Schema requiring periodic: boolean const false) before validation","Add a CI check that all checked-in recipes contain the field"],"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"}