affaan-m/ECC · error · ContractError
missing modality manifests
Error message
missing modality manifests: {sorted(missing)} What it means
validate_manifests requires a JSON manifest for every required modality (image, video, 3D asset) in the manifests directory. If one or more <modality>.json files are absent, this ContractError lists the missing stems so the bundle cannot pass without complete dry-run manifests.
Solutions
- Generate the missing <modality>.json manifest(s) into the manifests directory before validating.
- Check the manifests_dir path passed to validate_manifests/validate_bundle points at the directory that actually contains the files.
- Rename typo'd manifests to exactly match the required modality names (image.json, video.json, 3d_asset.json).
- Rerun the full bundle build so all modality manifest generators execute, then validate.
Example fix
// before
# manifests dir contains only image.json, video.json
// after
# add manifests/3d_asset.json with {"modality": "3d_asset", "requests": [...], "dry_run": true, ...} Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
REQUIRED = {"image", "video", "3d_asset"}
def manifests_ready(d):
p = Path(d)
found = {f.stem for f in p.glob("*.json")} if p.is_dir() else set()
missing = REQUIRED - found
if missing:
raise FileNotFoundError(f"missing manifests: {sorted(missing)}") Try / catch
try:
validate_manifests(manifests_dir)
except ContractError as e:
if "missing modality manifests" in str(e):
generate_all_manifests(manifests_dir) # rerun generation step
validate_manifests(manifests_dir)
else:
raise Prevention
- Run the manifest generation step as a hard prerequisite of the bundle build.
- Use exact modality filenames (image.json, video.json, 3d_asset.json) — no renames or typos.
- Check the manifests directory exists and is populated in CI before validation.
When it happens
Trigger: Running validate_manifests('path/to/manifests') or validate_bundle() where the directory is missing entirely, is not a directory, or lacks one of image.json / video.json / 3d_asset.json (per _REQUIRED_MODALITIES).
Common situations: Forgetting to run the manifest-generation step before validation; manifests written to the wrong directory; a modality generator skipped due to an earlier failure; typo in a manifest filename (e.g. 'three_d.json'); empty manifests dir because generation was dry-run-skipped.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- assets must be a nonempty list
- Duplicate asset id
- Each asset must be an object
- ECC_DRY_RUN must be "1" or "0" when set
- effect recipe crosses the dry-run provider boundary
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/63a1ba45ea975a4a.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:287
if not isinstance(digest, str) or len(digest) != 64:
raise ContractError(f"rule {rule.get('rule_id')} lacks immutable reference evidence")
times = item.get("times")
if not isinstance(times, list) or not times:
raise ContractError(f"rule {rule.get('rule_id')} lacks time evidence")
source_duration = item.get("source_duration")
for time in times:
_validate_media_time(
time, source_duration, label=f"rule {rule.get('rule_id')} time evidence"
)
def validate_manifests(manifests_dir: str | Path) -> None:
"""Require image, video, and 3D-asset dry-run request manifests."""
manifests_dir = Path(manifests_dir)
found = {path.stem for path in manifests_dir.glob("*.json")} if manifests_dir.is_dir() else set()
missing = _REQUIRED_MODALITIES - found
if missing:
raise ContractError(f"missing modality manifests: {sorted(missing)}")
for modality in _REQUIRED_MODALITIES:
payload = json.loads((manifests_dir / f"{modality}.json").read_text(encoding="utf-8"))
if payload.get("modality") != modality or not payload.get("requests"):
raise ContractError(f"invalid or empty {modality} manifest")
if (payload.get("dry_run") is not True or payload.get("submit") is not False
or type(payload.get("provider_calls")) is not int
or payload.get("provider_calls") != 0
or payload.get("provider_execution") is not False):
raise ContractError(f"{modality} manifest crosses the dry-run boundary")
for request in payload["requests"]:
if (request.get("dry_run") is not True
or request.get("submit") is not False
or type(request.get("provider_calls")) is not int
or request.get("provider_calls") != 0
or request.get("provider_execution") is not False
or request.get("provider_call_mode") != "disabled"):
raise ContractError(f"{modality} request crosses the dry-run boundary")
View on GitHub (pinned to 8321021c54)