affaan-m/ECC · error · ContractError
genre has an empty signature axis, including avoid
Error message
genre {spec.get('number')} has an empty signature axis, including avoid What it means
Every signature axis must be a non-empty list — including the avoid axis, as the message states. validate_genre_specs raises this ContractError when any signature[axis] is an empty list, because an empty axis means the style signature carries no information (or no explicit avoidances) and cannot discriminate this genre from others.
Solutions
- Populate every axis with at least one descriptor from the signature analysis.
- Add explicit avoid descriptors to the avoid axis (e.g. ["harsh-distortion"]) rather than leaving it empty.
- Re-run the signature extraction for the spec if the analysis produced no matches.
- Pre-check before validating: all(len(signature[axis]) > 0 for axis in axes).
Example fix
// before
spec["signature"] = {"palette": ["warm"], "avoid": []}
// after
spec["signature"] = {"palette": ["warm"], "avoid": ["harsh-distortion"]} Defensive patterns
Strategy: validation
Validate before calling
def axes_are_nonempty(spec, axes):
sig = spec.get("signature") or {}
return all(isinstance(sig.get(a), list) and sig[a] for a in axes) Type guard
def is_nonempty_axis(value) -> bool:
return isinstance(value, list) and len(value) > 0 Try / catch
try:
validate_genre_specs(specs)
except ContractError as e:
if "empty signature axis" in str(e):
sig = spec["signature"]
for a, v in sig.items():
if not v:
raise ValueError(f"spec {spec.get('number')}: axis '{a}' is empty; re-run signature extraction")
raise Prevention
- Re-run signature extraction when an axis matches zero descriptors.
- Always author at least one explicit avoid entry — empty avoid lists are rejected.
- Check filtering logic for stripping all descriptors from an axis.
- Pre-validate with all(len(sig[a]) > 0 for a in axes) before bundle validation.
When it happens
Trigger: Calling validate_genre_specs / validate_bundle with any signature axis set to [], e.g. {"palette": [], "rhythm": [...], "avoid": []}.
Common situations: A signature-extraction run that matched zero descriptors for an axis and wrote the empty list; the mistaken belief that an empty avoid list means 'avoid nothing' — the contract requires explicit avoid entries; filtering logic that stripped all descriptors of one axis.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- apply_local needs at least one media clip
- genre has no references
- genre has an incomplete signature
- genre signature axes must be lists
- workflow needs at least one numbered genre
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/ec595965aa6367ce.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:170
_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):
raise ContractError(f"genre {spec.get('number')} has an incomplete signature")
if not all(isinstance(signature[axis], list) for axis in _SIGNATURE_AXES):
raise ContractError(f"genre {spec.get('number')} signature axes must be lists")
if not all(signature[axis] for axis in _SIGNATURE_AXES):
raise ContractError(f"genre {spec.get('number')} has an empty signature axis, including avoid")
def validate_effect_recipe(
recipe: dict[str, Any], *, reference_durations: dict[str, float] | None = None
) -> None:
"""Require a seeded aperiodic schedule and anchors on subject-aware effects."""
if (recipe.get("dry_run") is not True
or type(recipe.get("provider_calls")) is not int
or recipe.get("provider_calls") != 0
or recipe.get("provider_execution") is not False):
raise ContractError("effect recipe crosses the dry-run provider boundary")
if not isinstance(recipe.get("seed"), int) or isinstance(recipe.get("seed"), bool):
raise ContractError("effect recipe must have an integer seed")
if recipe.get("rng_algorithm") != "python.random.Random/v1":
raise ContractError("effect recipe must declare its seeded RNG algorithm")
events = recipe.get("events")
if not isinstance(events, list) or len(events) < 3:
raise ContractError("effect recipe needs at least three scheduled events")View on GitHub (pinned to 8321021c54)