{"record":{"id":"70e840b40bccbf2f","repo":"nextlevelbuilder/ui-ux-pro-max-skill","slug":"decision-rules-must-be-a-json-object","errorCode":null,"errorMessage":"decision rules must be a JSON object","messagePattern":"decision rules must be a JSON object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/ui-ux-pro-max/scripts/reasoning_contract.py","lineNumber":74,"sourceCode":"\n\ndef _object_without_duplicates(pairs):\n    result = {}\n    for key, value in pairs:\n        if key in result:\n            raise ValueError(\"duplicate decision-rule key: {}\".format(key))\n        result[key] = value\n    return result\n\n\ndef parse_decision_rules(raw):\n    \"\"\"Parse the canonical condition -> action-array representation.\"\"\"\n    try:\n        rules = json.loads(raw or \"{}\", object_pairs_hook=_object_without_duplicates)\n    except json.JSONDecodeError as error:\n        raise ValueError(\"invalid decision-rule JSON: {}\".format(error)) from error\n    if not isinstance(rules, dict):\n        raise ValueError(\"decision rules must be a JSON object\")\n    for condition, actions in rules.items():\n        if condition not in ALLOWED_CONDITIONS:\n            raise ValueError(\"unknown decision-rule condition: {}\".format(condition))\n        if not isinstance(actions, list) or not actions:\n            raise ValueError(\"{} must map to a non-empty action array\".format(condition))\n        for action in actions:\n            _validate_action(action)\n        if len(actions) != len(set(actions)):\n            raise ValueError(\"{} contains duplicate actions\".format(condition))\n    return rules\n\n\ndef _validate_action(action):\n    if not isinstance(action, str) or \":\" not in action:\n        raise ValueError(\"action must use a known prefix: {}\".format(action))\n    prefix, value = action.split(\":\", 1)\n    if prefix not in ACTION_PREFIXES:\n        raise ValueError(\"unknown decision-rule action: {}\".format(action))","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5/src/ui-ux-pro-max/scripts/reasoning_contract.py#L56-L92","documentation":"After successful JSON parsing, parse_decision_rules requires the top-level value to be a dict. If the JSON is valid but is an array, string, number, or null, this ValueError fires. The grammar is condition-name -> action-array, so any other top-level shape is a contract violation.","triggerScenarios":"parse_decision_rules('[\"mode:dark\"]') — a bare action array instead of an object; parse_decision_rules('\"if_mobile\"'); parse_decision_rules('null') (note: raw=None is coerced to '{}', but the literal string 'null' parses to None and fails this check).","commonSituations":"A caller stores only the actions list and forgets the condition wrapper; downstream code JSON-encodes a list variable that was supposed to be a rules dict.","solutions":["Wrap the payload as an object keyed by condition: `{\"must_have\": [...]}` or `{\"if_mobile\": [...]}`.","Fix the producer to serialize the rules dict, not a list of actions.","Add an assertion/isinstance check at the call site before passing user-supplied JSON to parse_decision_rules."],"exampleFix":"# before\nrules = parse_decision_rules(json.dumps([\"mode:dark\"]))\n\n# after\nrules = parse_decision_rules(json.dumps({\"must_have\": [\"mode:dark\"]}))","handlingStrategy":"type-guard","validationCode":"import json\nparsed = json.loads(raw)\nif not isinstance(parsed, dict):\n    raise ValueError(f\"decision rules must be an object, got {type(parsed).__name__}\")","typeGuard":"def is_rules_object(parsed) -> bool:\n    return isinstance(parsed, dict) and all(isinstance(v, list) and v for v in parsed.values())","tryCatchPattern":"try:\n    rules = rc.parse_decision_rules(raw)\nexcept ValueError as exc:\n    if 'must be a JSON object' in str(exc):\n        parsed = json.loads(raw)\n        raw = json.dumps({\"must_have\": parsed if isinstance(parsed, list) else [parsed]})\n        rules = rc.parse_decision_rules(raw)  # only if this reshape matches your intent","preventionTips":["Serialize the whole rules dict from typed Python data instead of piecing JSON together.","Assert isinstance(payload, dict) at the boundary where rules enter your pipeline."],"tags":["json","type-error","decision-rules","validation"],"backgroundTag":null,"analyzedSha":"a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5","analyzedAt":"2026-08-14T18:51:02.321Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}