{"record":{"id":"d11c7150fb695ed5","repo":"affaan-m/ECC","slug":"genre-spec-get-number-signature-axes-must-be-lists","errorCode":null,"errorMessage":"genre {spec.get('number')} signature axes must be lists","messagePattern":"genre (.+?) signature axes must be lists","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":168,"sourceCode":"                        raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                    for time in group.get(\"times\", []):\n                        _validate_media_time(\n                            time, group.get(\"source_duration\"),\n                            label=f\"genre {spec.get('number')} time evidence\",\n                        )\n            temporal = measured.get(\"temporal\", {})\n            if isinstance(temporal, dict):\n                for group in temporal.get(\"scene_change_evidence\", []):\n                    for time in group.get(\"times\", []):\n                        _validate_media_time(\n                            time, group.get(\"source_duration\"),\n                            label=f\"genre {spec.get('number')} scene evidence\",\n                        )\n        signature = spec.get(\"signature\")\n        if not isinstance(signature, dict) or not _SIGNATURE_AXES.issubset(signature):\n            raise ContractError(f\"genre {spec.get('number')} has an incomplete signature\")\n        if not all(isinstance(signature[axis], list) for axis in _SIGNATURE_AXES):\n            raise ContractError(f\"genre {spec.get('number')} signature axes must be lists\")\n        if not all(signature[axis] for axis in _SIGNATURE_AXES):\n            raise ContractError(f\"genre {spec.get('number')} has an empty signature axis, including avoid\")\n\n\ndef validate_effect_recipe(\n    recipe: dict[str, Any], *, reference_durations: dict[str, float] | None = None\n) -> None:\n    \"\"\"Require a seeded aperiodic schedule and anchors on subject-aware effects.\"\"\"\n    if (recipe.get(\"dry_run\") is not True\n            or type(recipe.get(\"provider_calls\")) is not int\n            or recipe.get(\"provider_calls\") != 0\n            or recipe.get(\"provider_execution\") is not False):\n        raise ContractError(\"effect recipe crosses the dry-run provider boundary\")\n    if not isinstance(recipe.get(\"seed\"), int) or isinstance(recipe.get(\"seed\"), bool):\n        raise ContractError(\"effect recipe must have an integer seed\")\n    if recipe.get(\"rng_algorithm\") != \"python.random.Random/v1\":\n        raise ContractError(\"effect recipe must declare its seeded RNG algorithm\")\n    events = recipe.get(\"events\")","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L150-L186","documentation":"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.","triggerScenarios":"Calling validate_genre_specs / validate_bundle with signature = {\"palette\": \"warm\", ...} (string instead of list) or any axis set to a number/dict/None.","commonSituations":"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.","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())."],"exampleFix":"// before\nspec[\"signature\"][\"palette\"] = \"warm\"\n// after\nspec[\"signature\"][\"palette\"] = [\"warm\"]","handlingStrategy":"type-guard","validationCode":"def axes_are_lists(spec, axes):\n    sig = spec.get(\"signature\") or {}\n    return all(isinstance(sig.get(a), list) for a in axes)","typeGuard":"def is_axis_descriptor_list(value) -> bool:\n    return isinstance(value, list) and all(isinstance(v, str) for v in value)","tryCatchPattern":"try:\n    validate_genre_specs(specs)\nexcept ContractError as e:\n    if \"must be lists\" in str(e):\n        sig = spec[\"signature\"]\n        for a, v in sig.items():\n            if not isinstance(v, list):\n                sig[a] = [v]\n    raise","preventionTips":["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."],"tags":["validation","type-mismatch","signature"],"backgroundTag":"type-mismatch","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"}