{"record":{"id":"588137b8640e92bd","repo":"affaan-m/ECC","slug":"effect-recipe-needs-at-least-three-scheduled-events","errorCode":null,"errorMessage":"effect recipe needs at least three scheduled events","messagePattern":"effect recipe needs at least three scheduled events","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":188,"sourceCode":"            raise ContractError(f\"genre {spec.get('number')} has an empty signature axis, including avoid\")\n\n\ndef 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):","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L170-L206","documentation":"An effect recipe must schedule at least three events; fewer events cannot demonstrate an aperiodic (non-periodic) schedule, which the contract requires. A missing or non-list 'events' key, or a list with 0-2 entries, triggers this error.","triggerScenarios":"validate_effect_recipe called with a recipe where 'events' is absent, is not a list, or contains fewer than 3 event dicts.","commonSituations":"Minimal test recipes written by hand with one or two events; a generator bug truncating the event list; deserialization dropping events on failure; users trimming events to simplify a fixture.","solutions":["Provide at least three event dicts in recipe['events'].","Check that the recipe generator actually emitted all scheduled events (log len(events) before validating).","If a small fixture is needed, pad with additional valid staggered events."],"exampleFix":"# before\nevents = [{\"time\": 0.0, \"duration\": 1.0}]\n# after\nevents = recipe_events  # list with >= 3 event dicts\nassert isinstance(events, list) and len(events) >= 3","handlingStrategy":"validation","validationCode":"events = recipe.get(\"events\")\nif not isinstance(events, list) or len(events) < 3:\n    raise ValueError(f\"recipe needs >= 3 events, got {len(events) if isinstance(events, list) else type(events).__name__}\")","typeGuard":"def has_enough_events(recipe: dict) -> bool:\n    events = recipe.get(\"events\")\n    return isinstance(events, list) and len(events) >= 3","tryCatchPattern":"try:\n    validate_effect_recipe(recipe)\nexcept ContractError as exc:\n    if \"at least three scheduled events\" in str(exc):\n        recipe = {**recipe, \"events\": pad_to_three_events(recipe.get(\"events\", []))}\n        validate_effect_recipe(recipe)\n    else:\n        raise","preventionTips":["Assert len(events) >= 3 right after generating the schedule.","Check generator logs for truncated event lists.","Keep fixture recipes at 3+ staggered events."],"tags":["validation","events","schedule"],"backgroundTag":"value-out-of-range","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"}