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

  1. Populate every axis with at least one descriptor from the signature analysis.
  2. Add explicit avoid descriptors to the avoid axis (e.g. ["harsh-distortion"]) rather than leaving it empty.
  3. Re-run the signature extraction for the spec if the analysis produced no matches.
  4. 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

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


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)