affaan-m/ECC · error · ContractError

effect event times must be unique and increasing

Error message

effect event times must be unique and increasing

What it means

ContractError raised by validate_effect_recipe when the ordered list of effect event times is either not sorted ascending or contains duplicate times. The tasteforge contract requires every stochastic effect schedule to be a strictly increasing sequence of unique times so intervals between events are well-defined. The check computes float(event['time']) for each event and compares against its sorted, deduplicated forms.

Solutions

  1. Sort events by 'time' ascending before validation: events.sort(key=lambda e: float(e['time'])).
  2. Deduplicate events sharing identical time values, or offset colliding times (e.g. add the minimum representable delta).
  3. Regenerate the recipe with a generator that emits monotonically increasing times.
  4. Catch ContractError in a validation pass and report the offending pair of times to the recipe author.

Example fix

// before
events = [
  {"time": 2.5, "effect": "cv_blur"},
  {"time": 1.0, "effect": "cv_noise"},
]
// after
events = sorted(
  [{"time": 2.5, "effect": "cv_blur"}, {"time": 1.0, "effect": "cv_noise"}],
  key=lambda e: float(e["time"]),
)
Defensive patterns

Strategy: validation

Validate before calling

times = [float(e["time"]) for e in recipe["events"]]
if times != sorted(times) or len(times) != len(set(times)):
    raise ValueError("effect event times must be unique and strictly increasing")

Type guard

def has_strictly_increasing_times(events) -> bool:
    try:
        times = [float(e["time"]) for e in events]
    except (KeyError, TypeError, ValueError):
        return False
    return all(b > a for a, b in zip(times, times[1:]))

Try / catch

try:
    validate_effect_recipe(recipe, reference_durations)
except ContractError as exc:
    if "unique and increasing" in str(exc):
        recipe["events"] = sorted(dedupe_by_time(recipe["events"]), key=lambda e: float(e["time"]))
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_effect_recipe (directly or via validate_bundle) with a recipe whose events array has two events with the same 'time', or times listed out of ascending order (e.g. [{time: 5.0},{time: 2.0}]). Also triggered by times that compare equal after float() coercion (e.g. 1 and 1.0).

Common situations: Hand-authored recipe JSON where events were appended out of order; programmatic generation that shuffles or deduplicates events incorrectly; merging two event lists without re-sorting; copy-pasting an event and forgetting to change its time.

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/104fd2e6a0be37ce. Report an issue: GitHub.

Appendix: source

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

            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"
        )
        if reference_durations is not None:
            digest = evidence.get("reference_sha256")
            expected_duration = reference_durations.get(digest) if isinstance(digest, str) else None
            if expected_duration is None or evidence.get("source_duration") != expected_duration:
                raise ContractError("effect evidence source duration is not bound to its receipt reference")
    times = [float(event["time"]) for event in events]
    if times != sorted(times) or len(times) != len(set(times)):
        raise ContractError("effect event times must be unique and increasing")
    intervals = [round(b - a, 6) for a, b in zip(times, times[1:])]  # noqa: RUF007
    if len(set(intervals)) <= 1:
        raise ContractError("stochastic schedule is periodic; intervals must vary")
    for period in range(1, len(intervals) // 2 + 1):
        if all(intervals[index] == intervals[index % period] for index in range(len(intervals))):
            raise ContractError("stochastic schedule is periodic; repeating interval cycle")
    if recipe.get("periodic") is not False:
        raise ContractError("effect recipe must explicitly declare periodic=false")
    for event in events:
        cv_effect = str(event.get("effect", "")).startswith("cv_")
        if cv_effect and event.get("requires_subject_anchor") is not True:
            raise ContractError(f"CV effect {event.get('effect')} must require a subject anchor")
        if event.get("requires_subject_anchor"):
            anchor = event.get("subject_anchor")
            required = {
                "mode", "target", "source_ref_sha256", "evidence_time",
                "source_duration", "lost_policy",
            }

View on GitHub (pinned to 8321021c54)