affaan-m/ECC · error · ContractError

genre has invalid total duration

Error message

genre {spec.get('number')} has invalid total duration

What it means

When a spec's measured_features contains total_duration, it must be a finite real number. _is_finite_real rejects None, non-numeric types, NaN, and infinity. The validator raises this ContractError so downstream timing math never operates on missing or non-finite durations.

Solutions

  1. Ensure the measurement pipeline produces a real finite float for total_duration and fix the NaN/inf source (often a division by zero).
  2. Parse string durations: total_duration = float(raw) after validating it is numeric.
  3. If no duration is known yet, omit the key or set measured_features to None rather than passing NaN.
  4. Add math.isfinite(total_duration) at the point where evidence is recorded to fail early.

Example fix

// before
spec["measured_features"]["total_duration"] = float("nan")
// after
import math
duration = compute_duration()
assert math.isfinite(duration) and duration > 0
spec["measured_features"]["total_duration"] = duration
Defensive patterns

Strategy: validation

Validate before calling

import math

def duration_is_finite(spec):
    m = spec.get("measured_features") or {}
    d = m.get("total_duration")
    return isinstance(d, (int, float)) and not isinstance(d, bool) and math.isfinite(d)

Type guard

def is_finite_duration(value) -> bool:
    return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)

Try / catch

try:
    validate_genre_specs(specs)
except ContractError as e:
    if "invalid total duration" in str(e):
        for s in specs:
            d = (s.get("measured_features") or {}).get("total_duration")
            if not math.isfinite(float(d)):
                raise ValueError(f"re-measure spec {s.get('number')}: duration={d!r}")
    raise

Prevention

When it happens

Trigger: Calling validate_genre_specs / validate_bundle with measured_features containing total_duration set to None, a string like "3.5", NaN/inf (e.g. from a failed measurement or 0-division), or the key holding a non-numeric object.

Common situations: A measurement step that failed and wrote NaN (e.g. dividing by zero duration); JSON configs where total_duration was quoted as a string; missing key resolved via .get to None; serialization of float('inf') from earlier computations.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


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

Appendix: source

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

    if len(numbers) != len(set(numbers)):
        raise ContractError("genre numbers must be distinct")

    fingerprints = [spec.get("style_fingerprint") for spec in specs]
    signatures = [_semantic_signature(spec) for spec in specs]
    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):
        raise ContractError("genre references collapsed into a generic style; distinct specs required")

    for spec in specs:
        if spec.get("dry_run") is not True:
            raise ContractError(f"genre {spec.get('number')} crosses the dry-run boundary")
        measured = spec.get("measured_features")
        if measured is not None:
            if not isinstance(measured, dict):
                raise ContractError(f"genre {spec.get('number')} has invalid measured evidence")
            _validate_numeric_evidence(measured, label=f"genre {spec.get('number')} evidence")
            total_duration = measured.get("total_duration")
            if not _is_finite_real(total_duration):
                raise ContractError(f"genre {spec.get('number')} has invalid total duration")
            total_duration = cast(float, total_duration)
            if float(total_duration) <= 0:
                raise ContractError(f"genre {spec.get('number')} has invalid total duration")
            for group_name in ("sample_times",):
                groups = measured.get(group_name, [])
                if not isinstance(groups, list):
                    raise ContractError(f"genre {spec.get('number')} has invalid time evidence")
                for group in groups:
                    if not isinstance(group, dict):
                        raise ContractError(f"genre {spec.get('number')} has invalid time evidence")
                    for time in group.get("times", []):
                        _validate_media_time(
                            time, group.get("source_duration"),
                            label=f"genre {spec.get('number')} time evidence",
                        )
            temporal = measured.get("temporal", {})
            if isinstance(temporal, dict):
                for group in temporal.get("scene_change_evidence", []):

View on GitHub (pinned to 8321021c54)