{"record":{"id":"104fd2e6a0be37ce","repo":"affaan-m/ECC","slug":"effect-event-times-must-be-unique-and-increasing","errorCode":null,"errorMessage":"effect event times must be unique and increasing","messagePattern":"effect event times must be unique and increasing","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":218,"sourceCode":"            raise ContractError(\"effect event start and duration must be finite positive timeline values\")\n        start = cast(float, start)\n        duration = cast(float, duration)\n        if float(start) + float(duration) > float(timeline) + 1e-9:\n            raise ContractError(\"effect event end exceeds the declared timeline\")\n        evidence = event.get(\"evidence\")\n        if not isinstance(evidence, dict):\n            raise ContractError(f\"effect {event.get('effect')} lacks reference evidence\")\n        _validate_media_time(\n            evidence.get(\"time\"), evidence.get(\"source_duration\"), label=\"effect evidence time\"\n        )\n        if reference_durations is not None:\n            digest = evidence.get(\"reference_sha256\")\n            expected_duration = reference_durations.get(digest) if isinstance(digest, str) else None\n            if expected_duration is None or evidence.get(\"source_duration\") != expected_duration:\n                raise ContractError(\"effect evidence source duration is not bound to its receipt reference\")\n    times = [float(event[\"time\"]) for event in events]\n    if times != sorted(times) or len(times) != len(set(times)):\n        raise ContractError(\"effect event times must be unique and increasing\")\n    intervals = [round(b - a, 6) for a, b in zip(times, times[1:])]  # noqa: RUF007\n    if len(set(intervals)) <= 1:\n        raise ContractError(\"stochastic schedule is periodic; intervals must vary\")\n    for period in range(1, len(intervals) // 2 + 1):\n        if all(intervals[index] == intervals[index % period] for index in range(len(intervals))):\n            raise ContractError(\"stochastic schedule is periodic; repeating interval cycle\")\n    if recipe.get(\"periodic\") is not False:\n        raise ContractError(\"effect recipe must explicitly declare periodic=false\")\n    for event in events:\n        cv_effect = str(event.get(\"effect\", \"\")).startswith(\"cv_\")\n        if cv_effect and event.get(\"requires_subject_anchor\") is not True:\n            raise ContractError(f\"CV effect {event.get('effect')} must require a subject anchor\")\n        if event.get(\"requires_subject_anchor\"):\n            anchor = event.get(\"subject_anchor\")\n            required = {\n                \"mode\", \"target\", \"source_ref_sha256\", \"evidence_time\",\n                \"source_duration\", \"lost_policy\",\n            }","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L200-L236","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Sort events by 'time' ascending before validation: events.sort(key=lambda e: float(e['time'])).","Deduplicate events sharing identical time values, or offset colliding times (e.g. add the minimum representable delta).","Regenerate the recipe with a generator that emits monotonically increasing times.","Catch ContractError in a validation pass and report the offending pair of times to the recipe author."],"exampleFix":"// before\nevents = [\n  {\"time\": 2.5, \"effect\": \"cv_blur\"},\n  {\"time\": 1.0, \"effect\": \"cv_noise\"},\n]\n// after\nevents = sorted(\n  [{\"time\": 2.5, \"effect\": \"cv_blur\"}, {\"time\": 1.0, \"effect\": \"cv_noise\"}],\n  key=lambda e: float(e[\"time\"]),\n)","handlingStrategy":"validation","validationCode":"times = [float(e[\"time\"]) for e in recipe[\"events\"]]\nif times != sorted(times) or len(times) != len(set(times)):\n    raise ValueError(\"effect event times must be unique and strictly increasing\")","typeGuard":"def has_strictly_increasing_times(events) -> bool:\n    try:\n        times = [float(e[\"time\"]) for e in events]\n    except (KeyError, TypeError, ValueError):\n        return False\n    return all(b > a for a, b in zip(times, times[1:]))","tryCatchPattern":"try:\n    validate_effect_recipe(recipe, reference_durations)\nexcept ContractError as exc:\n    if \"unique and increasing\" in str(exc):\n        recipe[\"events\"] = sorted(dedupe_by_time(recipe[\"events\"]), key=lambda e: float(e[\"time\"]))\n    else:\n        raise","preventionTips":["Always sort events by time immediately after building the events list","Deduplicate events by time before validation","Write a unit test asserting strictly increasing times for every generated recipe","Reject recipes at authoring time with a clear message rather than at bundle validation"],"tags":["validation","contract","data-integrity"],"backgroundTag":"invalid-argument-value","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"}