affaan-m/ECC · error · ContractError
genre signature axes must be lists
Error message
genre {spec.get('number')} signature axes must be lists What it means
Once the signature dict is present and complete, every axis value must be a list. validate_genre_specs raises this ContractError when any signature[axis] is a string, scalar, or other non-list type. Axes are treated as collections of descriptors (including the avoid axis), so single values must still be wrapped in a list.
Solutions
- Wrap each axis value in a list, e.g. palette: ["warm"] instead of palette: "warm".
- Check the signature-producing code for assignments that unwrap lists (e.g. axis = descriptors[0]).
- Normalize at load time: for each axis, value if isinstance(value, list) else [value] — but prefer fixing the source.
- Run a quick pre-check: all(isinstance(v, list) for v in signature.values()).
Example fix
// before spec["signature"]["palette"] = "warm" // after spec["signature"]["palette"] = ["warm"]
Defensive patterns
Strategy: type-guard
Validate before calling
def axes_are_lists(spec, axes):
sig = spec.get("signature") or {}
return all(isinstance(sig.get(a), list) for a in axes) Type guard
def is_axis_descriptor_list(value) -> bool:
return isinstance(value, list) and all(isinstance(v, str) for v in value) Try / catch
try:
validate_genre_specs(specs)
except ContractError as e:
if "must be lists" in str(e):
sig = spec["signature"]
for a, v in sig.items():
if not isinstance(v, list):
sig[a] = [v]
raise Prevention
- Wrap single descriptors in lists when authoring configs: axis: [value].
- Check producer code for unwrapping assignments like axis = descriptors[0].
- Normalize axis values to lists at load time.
- Add a unit test asserting every axis value is a list.
When it happens
Trigger: Calling validate_genre_specs / validate_bundle with signature = {"palette": "warm", ...} (string instead of list) or any axis set to a number/dict/None.
Common situations: Hand-authored YAML where the axis was written as a bare string without [] brackets; a script that assigns the first descriptor directly instead of the full list; JSON round-trips that are fine but original authoring that skipped list syntax.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Canonical session snapshot must be an object
- Canonical session snapshot requires
- Canonical session snapshot requires
- Canonical session snapshot requires
- explicit opacity must be finite within 0..100
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/d11c7150fb695ed5.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:168
raise ContractError(f"genre {spec.get('number')} has invalid time evidence")
for time in group.get("times", []):
_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")View on GitHub (pinned to 8321021c54)