affaan-m/ECC · error · ContractError

genre has invalid time evidence

Error message

genre {spec.get('number')} has invalid time evidence

What it means

Under measured_features, the sample_times key (the only group checked) must be a list of time groups. This raise fires when sample_times exists but is not a list — e.g. a dict keyed by source name, a tuple-like JSON array mis-serialized, or a single group object passed directly. It is the first of the time-evidence structural checks.

Solutions

  1. Wrap the group(s) in a list: sample_times must be [{...}, ...].
  2. If groups are keyed by source, flatten the dict values into a list before assigning.
  3. If a single group was intended, wrap it: [group].
  4. Update the producer script that serializes sample_times to emit a JSON array.

Example fix

// before
spec["measured_features"]["sample_times"] = {"cam_a": [{"times": [1.0]}]}
// after
spec["measured_features"]["sample_times"] = [{"times": [1.0]}]
Defensive patterns

Strategy: validation

Validate before calling

def sample_times_is_list(spec):
    m = spec.get("measured_features") or {}
    st = m.get("sample_times", [])
    return isinstance(st, list)

Type guard

def is_time_group_list(value) -> bool:
    return isinstance(value, list)

Try / catch

try:
    validate_genre_specs(specs)
except ContractError as e:
    if "invalid time evidence" in str(e):
        m = spec["measured_features"]
        if isinstance(m.get("sample_times"), dict):
            m["sample_times"] = list(m["sample_times"].values())
    raise

Prevention

When it happens

Trigger: Calling validate_genre_specs / validate_bundle with measured_features.sample_times being a dict (e.g. {"src": [...]}) or a single group dict instead of a list of group dicts.

Common situations: Tooling that indexes sample times per source and writes the dict directly; a refactor changing sample_times from list to map without updating the contract; hand-authored configs nesting groups one level too deep.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

    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", []):
                    for time in group.get("times", []):
                        _validate_media_time(
                            time, group.get("source_duration"),
                            label=f"genre {spec.get('number')} scene evidence",
                        )
        signature = spec.get("signature")
        if not isinstance(signature, dict) or not _SIGNATURE_AXES.issubset(signature):

View on GitHub (pinned to 8321021c54)