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

  1. Provide at least three event dicts in recipe['events'].
  2. Check that the recipe generator actually emitted all scheduled events (log len(events) before validating).
  3. 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

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


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)