{"record":{"id":"7c080a51399d5bb3","repo":"affaan-m/ECC","slug":"genre-spec-get-number-has-invalid-time-evidence","errorCode":null,"errorMessage":"genre {spec.get('number')} has invalid time evidence","messagePattern":"genre (.+?) has invalid time evidence","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":147,"sourceCode":"\n    for spec in specs:\n        if spec.get(\"dry_run\") is not True:\n            raise ContractError(f\"genre {spec.get('number')} crosses the dry-run boundary\")\n        measured = spec.get(\"measured_features\")\n        if measured is not None:\n            if not isinstance(measured, dict):\n                raise ContractError(f\"genre {spec.get('number')} has invalid measured evidence\")\n            _validate_numeric_evidence(measured, label=f\"genre {spec.get('number')} evidence\")\n            total_duration = measured.get(\"total_duration\")\n            if not _is_finite_real(total_duration):\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            total_duration = cast(float, total_duration)\n            if float(total_duration) <= 0:\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            for group_name in (\"sample_times\",):\n                groups = measured.get(group_name, [])\n                if not isinstance(groups, list):\n                    raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                for group in groups:\n                    if not isinstance(group, dict):\n                        raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                    for time in group.get(\"times\", []):\n                        _validate_media_time(\n                            time, group.get(\"source_duration\"),\n                            label=f\"genre {spec.get('number')} time evidence\",\n                        )\n            temporal = measured.get(\"temporal\", {})\n            if isinstance(temporal, dict):\n                for group in temporal.get(\"scene_change_evidence\", []):\n                    for time in group.get(\"times\", []):\n                        _validate_media_time(\n                            time, group.get(\"source_duration\"),\n                            label=f\"genre {spec.get('number')} scene evidence\",\n                        )\n        signature = spec.get(\"signature\")\n        if not isinstance(signature, dict) or not _SIGNATURE_AXES.issubset(signature):","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L129-L165","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap the group(s) in a list: sample_times must be [{...}, ...].","If groups are keyed by source, flatten the dict values into a list before assigning.","If a single group was intended, wrap it: [group].","Update the producer script that serializes sample_times to emit a JSON array."],"exampleFix":"// before\nspec[\"measured_features\"][\"sample_times\"] = {\"cam_a\": [{\"times\": [1.0]}]}\n// after\nspec[\"measured_features\"][\"sample_times\"] = [{\"times\": [1.0]}]","handlingStrategy":"validation","validationCode":"def sample_times_is_list(spec):\n    m = spec.get(\"measured_features\") or {}\n    st = m.get(\"sample_times\", [])\n    return isinstance(st, list)","typeGuard":"def is_time_group_list(value) -> bool:\n    return isinstance(value, list)","tryCatchPattern":"try:\n    validate_genre_specs(specs)\nexcept ContractError as e:\n    if \"invalid time evidence\" in str(e):\n        m = spec[\"measured_features\"]\n        if isinstance(m.get(\"sample_times\"), dict):\n            m[\"sample_times\"] = list(m[\"sample_times\"].values())\n    raise","preventionTips":["Always serialize sample_times as a JSON array of group objects.","Flatten per-source dicts into lists before assigning.","Add a schema check in the tool that writes sample_times.","Keep the contract shape (list of dicts) documented in the producer."],"tags":["validation","type-mismatch","time-evidence"],"backgroundTag":"schema-validation-failed","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"}