{"record":{"id":"b03b640cda850af8","repo":"affaan-m/ECC","slug":"invalid-or-empty-modality-manifest","errorCode":null,"errorMessage":"invalid or empty {modality} manifest","messagePattern":"invalid or empty (.+?) manifest","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":291,"sourceCode":"                raise ContractError(f\"rule {rule.get('rule_id')} lacks time evidence\")\n            source_duration = item.get(\"source_duration\")\n            for time in times:\n                _validate_media_time(\n                    time, source_duration, label=f\"rule {rule.get('rule_id')} time evidence\"\n                )\n\n\ndef validate_manifests(manifests_dir: str | Path) -> None:\n    \"\"\"Require image, video, and 3D-asset dry-run request manifests.\"\"\"\n    manifests_dir = Path(manifests_dir)\n    found = {path.stem for path in manifests_dir.glob(\"*.json\")} if manifests_dir.is_dir() else set()\n    missing = _REQUIRED_MODALITIES - found\n    if missing:\n        raise ContractError(f\"missing modality manifests: {sorted(missing)}\")\n    for modality in _REQUIRED_MODALITIES:\n        payload = json.loads((manifests_dir / f\"{modality}.json\").read_text(encoding=\"utf-8\"))\n        if payload.get(\"modality\") != modality or not payload.get(\"requests\"):\n            raise ContractError(f\"invalid or empty {modality} manifest\")\n        if (payload.get(\"dry_run\") is not True or payload.get(\"submit\") is not False\n                or type(payload.get(\"provider_calls\")) is not int\n                or payload.get(\"provider_calls\") != 0\n                or payload.get(\"provider_execution\") is not False):\n            raise ContractError(f\"{modality} manifest crosses the dry-run boundary\")\n        for request in payload[\"requests\"]:\n            if (request.get(\"dry_run\") is not True\n                    or request.get(\"submit\") is not False\n                    or type(request.get(\"provider_calls\")) is not int\n                    or request.get(\"provider_calls\") != 0\n                    or request.get(\"provider_execution\") is not False\n                    or request.get(\"provider_call_mode\") != \"disabled\"):\n                raise ContractError(f\"{modality} request crosses the dry-run boundary\")\n\n\ndef validate_artifact_receipt(out_dir: str | Path, receipt: dict[str, Any]) -> None:\n    \"\"\"Verify that the receipt binds every emitted artifact and its provenance.\"\"\"\n    out_dir = Path(out_dir).resolve()","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L273-L309","documentation":"Each modality manifest must match its filename's modality and contain a non-empty 'requests' list. A manifest whose 'modality' field disagrees with the expected modality, or with no requests, raises this ContractError.","triggerScenarios":"image.json containing {\"modality\": \"video\", ...}; a manifest file that is valid JSON but an empty object {}; or {\"modality\": \"image\", \"requests\": []} passed through validate_manifests.","commonSituations":"Copying one manifest as a template for another without changing the 'modality' field; a generator crashing mid-write leaving an empty/partial JSON; requests filtered out upstream leaving an empty list; hand-editing that renamed the modality.","solutions":["Set the manifest's 'modality' field to exactly the expected value matching the filename (e.g. 'image' for image.json).","Populate 'requests' with at least one dry-run request entry.","Regenerate the manifest via the tool rather than copying another modality's manifest.","Validate the manifest JSON is complete (not truncated/empty) before running validation."],"exampleFix":"// before\n// video.json: {\"modality\": \"image\", \"requests\": []}\n// after\n// video.json: {\"modality\": \"video\", \"requests\": [{\"dry_run\": true, \"submit\": false, \"provider_calls\": 0, \"provider_execution\": false, \"provider_call_mode\": \"disabled\"}]}","handlingStrategy":"validation","validationCode":"def manifest_ok(path, expected_modality):\n    payload = json.loads(Path(path).read_text())\n    return (payload.get(\"modality\") == expected_modality\n            and isinstance(payload.get(\"requests\"), list)\n            and len(payload[\"requests\"]) > 0)","typeGuard":"def is_valid_manifest(payload: dict, modality: str) -> bool:\n    reqs = payload.get(\"requests\")\n    return payload.get(\"modality\") == modality and isinstance(reqs, list) and len(reqs) > 0","tryCatchPattern":"try:\n    validate_manifests(manifests_dir)\nexcept ContractError as e:\n    if \"invalid or empty\" in str(e):\n        regenerate_manifest_for(modality_from_message(e))\n        validate_manifests(manifests_dir)\n    else:\n        raise","preventionTips":["Generate each manifest from its own modality-specific template, never by copying another.","Verify the generator completed writing before validating (check file is non-empty, parses, and has requests).","Add a schema check for 'modality' matching the filename in your build script."],"tags":["manifest","schema","validation"],"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"}