{"record":{"id":"d988d5b824e8d25e","repo":"Hmbown/CodeWhale","slug":"kind-field-dotted-names-must-be-sorted-uniqu","errorCode":null,"errorMessage":"{kind} field `{dotted_names}` must be sorted unique non-empty strings","messagePattern":"(.+?) field `(.+?)` must be sorted unique non-empty strings","errorType":"exception","errorClass":"RuntimeContractError","httpStatus":null,"severity":"error","filePath":"scripts/check-runtime-contract-budget.py","lineNumber":222,"sourceCode":"def validate_identity_structure(document: dict[str, Any], kind: str) -> None:\n    profile = required_value(document, (\"tool_catalog\", \"surface_profile\"), kind)\n    if profile != TOOL_SURFACE_PROFILE:\n        raise RuntimeContractError(\n            f\"{kind} tool surface_profile must be `{TOOL_SURFACE_PROFILE}`, \"\n            f\"got {profile!r}\"\n        )\n\n    for mode, _label in VISIBLE_MODES:\n        for surface, _surface_label in TOOL_SURFACES:\n            base = (\"tool_catalog\", \"modes\", mode, surface)\n            names = required_value(document, (*base, \"tool_names\"), kind)\n            dotted_names = \".\".join((*base, \"tool_names\"))\n            if (\n                not isinstance(names, list)\n                or any(not isinstance(name, str) or not name for name in names)\n                or names != sorted(set(names))\n            ):\n                raise RuntimeContractError(\n                    f\"{kind} field `{dotted_names}` must be sorted unique non-empty strings\"\n                )\n            count = metric_value(document, (*base, \"tools\"), kind)\n            if count != len(names):\n                raise RuntimeContractError(\n                    f\"{kind} metric `{'.'.join((*base, 'tools'))}` must equal the \"\n                    f\"owned tool_names length ({len(names)})\"\n                )\n            digest = required_value(document, (*base, \"identity_sha256\"), kind)\n            expected = tool_identity_digest(names)\n            if digest != expected:\n                raise RuntimeContractError(\n                    f\"{kind} field `{'.'.join((*base, 'identity_sha256'))}` must \"\n                    \"match the owned sorted tool_names\"\n                )\n\n    for stage, _label in REPRESENTATIVE_STAGES:\n        path = (\"representative_context\", \"stages\", stage, \"identity_sha256\")","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/check-runtime-contract-budget.py#L204-L240","documentation":"Thrown by validate_identity_structure when validating either a measurement receipt or scripts/runtime-contract-budget.json. For every mode (plan/act/operate) and surface (full/active), tool_catalog.modes.<mode>.<surface>.tool_names must be a JSON array of non-empty strings that equals its own sorted(set(...)) - lexicographically sorted with no duplicates. The strictness makes the tool identity deterministic so the identity_sha256 digest and the ratchet comparison in compare() stay stable. It reaches the CLI as `[runtime-contract-budget] ERROR: ...` with exit code 2.","triggerScenarios":"Any call to compare(), validate_receipt(), validate_budget(), budget_from_receipt(), or run_measurement() (the default no-argument CLI run) where a tool_names entry is not a string, is empty, is duplicated, or is out of order - e.g. [\"read\", \"bash\", \"apply_patch\"]. Almost always a hand-edited budget JSON or a receipt from an older scripts/measure-runtime-contract.py.","commonSituations":"Hand-adding a newly introduced tool (the v0.9.8 `agent` tool pattern recorded in the budget _comment) at the wrong position; resolving a merge conflict in runtime-contract-budget.json so names get reordered; a stale receipt checked with --receipt after the measure script changed its ordering.","solutions":["Sort and deduplicate the exact tool_names array named in the message (tool_catalog.modes.<mode>.<surface>.tool_names)","Recompute identity_sha256 for that node, since the digest is taken over the sorted names (sha256 of NUL-joined list)","If the name set legitimately changed, update names, tools count, bytes, tokens_est, and identity_sha256 together in scripts/runtime-contract-budget.json and justify it in _comment (the file's existing convention)","Once compare() passes again, lock in the state with python3 scripts/check-runtime-contract-budget.py --update"],"exampleFix":"// before (scripts/runtime-contract-budget.json)\n\"tool_names\": [\"read\", \"bash\", \"apply_patch\"]\n\n// after - sorted, unique, non-empty\n\"tool_names\": [\"apply_patch\", \"bash\", \"read\"]","handlingStrategy":"validation","validationCode":"def tool_names_wellformed(doc: dict) -> bool:\n    for mode in (\"plan\", \"act\", \"operate\"):\n        for surface in (\"full\", \"active\"):\n            node = (\n                doc.get(\"tool_catalog\", {})\n                .get(\"modes\", {})\n                .get(mode, {})\n                .get(surface, {})\n            )\n            names = node.get(\"tool_names\")\n            if not isinstance(names, list):\n                return False\n            if any(not isinstance(n, str) or not n for n in names):\n                return False\n            if names != sorted(set(names)):\n                return False\n    return True\n\n# before compare(receipt, budget) or run_measurement()\nassert tool_names_wellformed(receipt) and tool_names_wellformed(budget)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-edit tool_names in runtime-contract-budget.json; change the catalog in code and re-measure","After any budget edit, run python3 -m json.tool scripts/runtime-contract-budget.json and the checker with --receipt on a known-good receipt","Treat tool_names as generated data: the only sanctioned writer is the measure script plus --update"],"tags":["python","json","validation","ci","ratchet"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}