{"record":{"id":"f40b2d8078fedac3","repo":"affaan-m/ECC","slug":"native-clip-snapshot-required","errorCode":null,"errorMessage":"native clip snapshot required","messagePattern":"native clip snapshot required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":216,"sourceCode":"    # Resolve's conventional decimal NTSC labels represent these exact rates.\n    ntsc = {\"23.976\": \"24000/1001\", \"29.97\": \"30000/1001\", \"59.94\": \"60000/1001\"}\n    try:\n        if type(native_fps) not in (str, int, float):\n            raise ValueError(\"native fps missing\")\n        label = str(native_fps)\n        if not re.fullmatch(r\"[0-9]{1,9}(?:\\.[0-9]{1,12}|/[1-9][0-9]{0,8})?\", label):\n            raise ValueError(\"native fps must use a bounded decimal or rational label\")\n        native_rate = Fraction(ntsc.get(label, label))\n        if native_rate != Fraction(*_rate(baseline[\"fps\"])):\n            raise ValueError(\"native fps differs\")\n    except (ValueError, ZeroDivisionError) as exc:\n        raise ValueError(\"native snapshot fps is missing, invalid or contradictory\") from exc\n    for cfg, key in [(\"project_name\", \"project\"), (\"timeline_name\", \"timeline\")]:\n        if _text(baseline[cfg]) != snapshot.get(key):\n            raise ValueError(\"native snapshot identity mismatch\")\n    tracks = snapshot.get(\"timeline_readback\")\n    if not isinstance(tracks, dict) or not tracks:\n        raise ValueError(\"native clip snapshot required\")\n    for track, clips in tracks.items():\n        if not re.fullmatch(r\"(?:video|audio)[1-9][0-9]*\", track) or not isinstance(clips, list):\n            raise ValueError(\"invalid native snapshot track\")\n        for clip in clips:\n            _check_clip(clip, bounds)\n    return tracks\n\n\ndef _check_clip(clip: Any, bounds: list[int]) -> None:\n    required = {\"path\", \"name\", \"start\", \"end\", \"left_offset\", \"right_offset\", \"enabled\", \"properties\"}\n    if not isinstance(clip, dict) or not required <= clip.keys():\n        raise ValueError(\"native snapshot clip is incomplete\")\n    _range([clip[\"start\"], clip[\"end\"]], bounds)\n    _integer(clip[\"left_offset\"])\n    _integer(clip[\"right_offset\"])\n    if clip[\"path\"] is not None:\n        _text(clip[\"path\"])\n    _text(clip[\"name\"])","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L198-L234","documentation":"_snapshot requires snapshot.timeline_readback to be a non-empty dict mapping track names to clip lists; this is the native clip evidence the whole preservation workflow binds against. Missing, empty, or wrong-typed readback means there is no original clip data to preserve, so the library refuses to build a bundle.","triggerScenarios":"build_application_bundle where the snapshot has no timeline_readback key, it is null, an empty object {}, or a list/string instead of a dict of track->clips.","commonSituations":"An empty or blank timeline exported; a snapshot from a tool that doesn't emit readback; a hand-built snapshot omitting the readback section; a truncated export that dropped the last field.","solutions":["Re-export the snapshot from a timeline that actually contains clips so timeline_readback is populated.","Add the timeline_readback object with keys like \"video1\", \"audio1\" mapping to clip lists.","Check the export wasn't truncated — validate the JSON is complete and non-empty.","Verify you're pointing at the snapshot for the intended timeline, not an empty placeholder."],"exampleFix":"// before\n{\"project\": \"P\", \"timeline\": \"T\", \"timeline_readback\": {}}\n\n// after\n{\"project\": \"P\", \"timeline\": \"T\", \"timeline_readback\": {\"video1\": [{\"path\": \"...\", \"name\": \"clip\", \"start\": 0, \"end\": 100, \"left_offset\": 0, \"right_offset\": 0, \"enabled\": true, \"properties\": {}}], \"audio1\": [...]}}","handlingStrategy":"validation","validationCode":"def has_readback(snapshot):\n    tracks = snapshot.get(\"timeline_readback\") if isinstance(snapshot, dict) else None\n    return isinstance(tracks, dict) and len(tracks) > 0 and all(isinstance(v, list) for v in tracks.values())","typeGuard":"def has_timeline_readback(snapshot) -> bool:\n    t = snapshot.get(\"timeline_readback\") if isinstance(snapshot, dict) else None\n    return isinstance(t, dict) and bool(t)","tryCatchPattern":"try:\n    bundle = build_application_bundle(config, None, local_only=True)\nexcept ValueError as exc:\n    if \"native clip snapshot required\" in str(exc):\n        print(\"timeline_readback missing/empty — re-export from a timeline with clips\")\n    raise","preventionTips":["Verify the timeline has clips before exporting a snapshot.","Validate snapshot completeness (readback present and non-empty) at export time.","Confirm file integrity after export (non-truncated, expected keys).","Never point at placeholder or template snapshots."],"tags":["snapshot","empty","validation"],"backgroundTag":"empty-required-field","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"}