affaan-m/ECC · error · ContractError
effect recipe needs at least three scheduled events
Error message
effect recipe needs at least three scheduled events
What it means
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.
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.
Example fix
# before
events = [{"time": 0.0, "duration": 1.0}]
# after
events = recipe_events # list with >= 3 event dicts
assert isinstance(events, list) and len(events) >= 3 Defensive patterns
Strategy: validation
Validate before calling
events = recipe.get("events")
if not isinstance(events, list) or len(events) < 3:
raise ValueError(f"recipe needs >= 3 events, got {len(events) if isinstance(events, list) else type(events).__name__}") Type guard
def has_enough_events(recipe: dict) -> bool:
events = recipe.get("events")
return isinstance(events, list) and len(events) >= 3 Try / catch
try:
validate_effect_recipe(recipe)
except ContractError as exc:
if "at least three scheduled events" in str(exc):
recipe = {**recipe, "events": pad_to_three_events(recipe.get("events", []))}
validate_effect_recipe(recipe)
else:
raise Prevention
- Assert len(events) >= 3 right after generating the schedule.
- Check generator logs for truncated event lists.
- Keep fixture recipes at 3+ staggered events.
When it happens
Trigger: validate_effect_recipe called with a recipe where 'events' is absent, is not a list, or contains fewer than 3 event dicts.
Common situations: 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.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- effect event end exceeds the declared timeline
- effect lacks reference evidence
- effect event start and duration must be finite positive…
- invalid cron expression
- -32602
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/588137b8640e92bd.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:188
raise ContractError(f"genre {spec.get('number')} has an empty signature axis, including avoid")
def validate_effect_recipe(
recipe: dict[str, Any], *, reference_durations: dict[str, float] | None = None
) -> None:
"""Require a seeded aperiodic schedule and anchors on subject-aware effects."""
if (recipe.get("dry_run") is not True
or type(recipe.get("provider_calls")) is not int
or recipe.get("provider_calls") != 0
or recipe.get("provider_execution") is not False):
raise ContractError("effect recipe crosses the dry-run provider boundary")
if not isinstance(recipe.get("seed"), int) or isinstance(recipe.get("seed"), bool):
raise ContractError("effect recipe must have an integer seed")
if recipe.get("rng_algorithm") != "python.random.Random/v1":
raise ContractError("effect recipe must declare its seeded RNG algorithm")
events = recipe.get("events")
if not isinstance(events, list) or len(events) < 3:
raise ContractError("effect recipe needs at least three scheduled events")
timeline = recipe.get("timeline_duration")
if not _is_finite_real(timeline):
raise ContractError("effect recipe must declare a finite positive timeline duration")
timeline = cast(float, timeline)
if float(timeline) <= 0:
raise ContractError("effect recipe must declare a finite positive timeline duration")
for event in events:
start = event.get("time")
duration = event.get("duration")
if (not _is_finite_real(start) or not _is_finite_real(duration)
or float(start) < 0 or float(duration) <= 0):
raise ContractError("effect event start and duration must be finite positive timeline values")
start = cast(float, start)
duration = cast(float, duration)
if float(start) + float(duration) > float(timeline) + 1e-9:
raise ContractError("effect event end exceeds the declared timeline")
evidence = event.get("evidence")
if not isinstance(evidence, dict):View on GitHub (pinned to 8321021c54)