affaan-m/ECC · error · ContractError

effect recipe must declare a finite positive timeline…

Error message

effect recipe must declare a finite positive timeline duration

What it means

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.

Solutions

  1. Set recipe['timeline_duration'] to a finite positive float (e.g. 30.0).
  2. Parse duration strings to numbers before validation (strip units, float(...)).
  3. Check upstream arithmetic for NaN/inf sources (math.isfinite guard).

Example fix

# before
recipe["timeline_duration"] = "30s"
# after
recipe["timeline_duration"] = 30.0
Defensive patterns

Strategy: validation

Validate before calling

tl = recipe.get("timeline_duration")
if not (isinstance(tl, (int, float)) and not isinstance(tl, bool) and math.isfinite(tl)):
    raise ValueError("timeline_duration must be a finite real number")

Type guard

def has_finite_timeline(recipe: dict) -> bool:
    tl = recipe.get("timeline_duration")
    return (isinstance(tl, (int, float)) and not isinstance(tl, bool)
        and math.isfinite(tl))

Try / catch

try:
    validate_effect_recipe(recipe)
except ContractError as exc:
    if "finite positive timeline duration" in str(exc):
        recipe = {**recipe, "timeline_duration": float(timeline_from_config)}
        validate_effect_recipe(recipe)
    else:
        raise

Prevention

When it happens

Trigger: validate_effect_recipe called with a recipe missing 'timeline_duration', or where it is None, a string like '30s', a bool, NaN, or +/-inf.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/439a9cb0dbc65d4f. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:191

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):
            raise ContractError(f"effect {event.get('effect')} lacks reference evidence")
        _validate_media_time(
            evidence.get("time"), evidence.get("source_duration"), label="effect evidence time"

View on GitHub (pinned to 8321021c54)