{"record":{"id":"f54799a7cf75fdbd","repo":"affaan-m/ECC","slug":"effect-recipe-crosses-the-dry-run-provider-boundary","errorCode":null,"errorMessage":"effect recipe crosses the dry-run provider boundary","messagePattern":"effect recipe crosses the dry-run provider boundary","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":181,"sourceCode":"                        )\n        signature = spec.get(\"signature\")\n        if not isinstance(signature, dict) or not _SIGNATURE_AXES.issubset(signature):\n            raise ContractError(f\"genre {spec.get('number')} has an incomplete signature\")\n        if not all(isinstance(signature[axis], list) for axis in _SIGNATURE_AXES):\n            raise ContractError(f\"genre {spec.get('number')} signature axes must be lists\")\n        if not all(signature[axis] for axis in _SIGNATURE_AXES):\n            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):","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L163-L199","documentation":"validate_effect_recipe enforces that every effect recipe operates in a strict dry-run mode: dry_run must be true, provider_calls must be exactly 0 (int), and provider_execution must be false. If any of these provider-boundary invariants is violated, the recipe is rejected because a 'dry-run' recipe that reaches the provider would produce nondeterministic, unverified outputs.","triggerScenarios":"Calling validate_effect_recipe (directly or via validate_bundle) with a recipe dict where dry_run is missing/false, provider_calls is absent, a bool, or non-zero, or provider_execution is missing/true.","commonSituations":"Hand-written recipes that forgot the dry_run flag after copying a live provider config; toggling provider_execution to true to 'test for real'; a generator emitting provider_calls counts as floats or omitting the field; merging recipes from an older schema version before these keys existed.","solutions":["Set recipe['dry_run'] = True in the recipe dict.","Ensure recipe['provider_calls'] is the exact int 0 (not 0.0, not True, not missing).","Ensure recipe['provider_execution'] = False.","Run the predicate (dry_run is True and type(provider_calls) is int and provider_calls == 0 and provider_execution is False) before calling the validator."],"exampleFix":"# before\nrecipe = {\"provider_calls\": 0, \"provider_execution\": False}\n# after\nrecipe = {\"dry_run\": True, \"provider_calls\": 0, \"provider_execution\": False}","handlingStrategy":"validation","validationCode":"def recipe_is_dry_run(recipe):\n    return (isinstance(recipe, dict)\n        and recipe.get(\"dry_run\") is True\n        and type(recipe.get(\"provider_calls\")) is int\n        and recipe.get(\"provider_calls\") == 0\n        and recipe.get(\"provider_execution\") is False)","typeGuard":"def is_dry_run_recipe(recipe: object) -> bool:\n    return (isinstance(recipe, dict)\n        and recipe.get(\"dry_run\") is True\n        and type(recipe.get(\"provider_calls\")) is int\n        and recipe.get(\"provider_calls\") == 0\n        and recipe.get(\"provider_execution\") is False)","tryCatchPattern":"try:\n    validate_effect_recipe(recipe)\nexcept ContractError as exc:\n    if \"dry-run provider boundary\" in str(exc):\n        recipe = {**recipe, \"dry_run\": True, \"provider_calls\": 0, \"provider_execution\": False}\n        validate_effect_recipe(recipe)\n    else:\n        raise","preventionTips":["Always template new recipes from a known-valid dry-run example.","Never flip provider_execution to true in recipes destined for the validator.","Log the offending keys (dry_run/provider_calls/provider_execution) before validating.","Add a unit test asserting generated recipes satisfy the dry-run predicate."],"tags":["validation","dry-run","config"],"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"}