{"record":{"id":"439a9cb0dbc65d4f","repo":"affaan-m/ECC","slug":"effect-recipe-must-declare-a-finite-positive-timeline","errorCode":null,"errorMessage":"effect recipe must declare a finite positive timeline duration","messagePattern":"effect recipe must declare a finite positive timeline duration","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":191,"sourceCode":"def validate_effect_recipe(\n    recipe: dict[str, Any], *, reference_durations: dict[str, float] | None = None\n) -> None:\n    \"\"\"Require a seeded aperiodic schedule and anchors on subject-aware effects.\"\"\"\n    if (recipe.get(\"dry_run\") is not True\n            or type(recipe.get(\"provider_calls\")) is not int\n            or recipe.get(\"provider_calls\") != 0\n            or recipe.get(\"provider_execution\") is not False):\n        raise ContractError(\"effect recipe crosses the dry-run provider boundary\")\n    if not isinstance(recipe.get(\"seed\"), int) or isinstance(recipe.get(\"seed\"), bool):\n        raise ContractError(\"effect recipe must have an integer seed\")\n    if recipe.get(\"rng_algorithm\") != \"python.random.Random/v1\":\n        raise ContractError(\"effect recipe must declare its seeded RNG algorithm\")\n    events = recipe.get(\"events\")\n    if not isinstance(events, list) or len(events) < 3:\n        raise ContractError(\"effect recipe needs at least three scheduled events\")\n    timeline = recipe.get(\"timeline_duration\")\n    if not _is_finite_real(timeline):\n        raise ContractError(\"effect recipe must declare a finite positive timeline duration\")\n    timeline = cast(float, timeline)\n    if float(timeline) <= 0:\n        raise ContractError(\"effect recipe must declare a finite positive timeline duration\")\n    for event in events:\n        start = event.get(\"time\")\n        duration = event.get(\"duration\")\n        if (not _is_finite_real(start) or not _is_finite_real(duration)\n                or float(start) < 0 or float(duration) <= 0):\n            raise ContractError(\"effect event start and duration must be finite positive timeline values\")\n        start = cast(float, start)\n        duration = cast(float, duration)\n        if float(start) + float(duration) > float(timeline) + 1e-9:\n            raise ContractError(\"effect event end exceeds the declared timeline\")\n        evidence = event.get(\"evidence\")\n        if not isinstance(evidence, dict):\n            raise ContractError(f\"effect {event.get('effect')} lacks reference evidence\")\n        _validate_media_time(\n            evidence.get(\"time\"), evidence.get(\"source_duration\"), label=\"effect evidence time\"","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L173-L209","documentation":"The recipe must declare timeline_duration as a finite real number (int or float, not NaN/inf, not bool/str). This first check fires when the value is missing, of the wrong type, or non-finite; the validator needs a usable timeline to bound all events.","triggerScenarios":"validate_effect_recipe called with a recipe missing 'timeline_duration', or where it is None, a string like '30s', a bool, NaN, or +/-inf.","commonSituations":"Parsing durations from config files where the unit suffix was kept as a string; a template that left the placeholder unfilled; JSON serialization of Infinity producing a non-standard token; dividing by zero upstream yielding inf.","solutions":["Set recipe['timeline_duration'] to a finite positive float (e.g. 30.0).","Parse duration strings to numbers before validation (strip units, float(...)).","Check upstream arithmetic for NaN/inf sources (math.isfinite guard)."],"exampleFix":"# before\nrecipe[\"timeline_duration\"] = \"30s\"\n# after\nrecipe[\"timeline_duration\"] = 30.0","handlingStrategy":"validation","validationCode":"tl = recipe.get(\"timeline_duration\")\nif not (isinstance(tl, (int, float)) and not isinstance(tl, bool) and math.isfinite(tl)):\n    raise ValueError(\"timeline_duration must be a finite real number\")","typeGuard":"def has_finite_timeline(recipe: dict) -> bool:\n    tl = recipe.get(\"timeline_duration\")\n    return (isinstance(tl, (int, float)) and not isinstance(tl, bool)\n        and math.isfinite(tl))","tryCatchPattern":"try:\n    validate_effect_recipe(recipe)\nexcept ContractError as exc:\n    if \"finite positive timeline duration\" in str(exc):\n        recipe = {**recipe, \"timeline_duration\": float(timeline_from_config)}\n        validate_effect_recipe(recipe)\n    else:\n        raise","preventionTips":["Parse duration strings (strip units) to floats before building the recipe.","Guard upstream duration math with math.isfinite.","Never leave timeline placeholders unfilled in templates."],"tags":["validation","timeline","type-safety"],"backgroundTag":"invalid-argument-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"}