Hmbown/CodeWhale · error · RuntimeContractError

{kind} field `{dotted_names}` must be sorted unique non-empt

Error message

{kind} field `{dotted_names}` must be sorted unique non-empty strings

What it means

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.

Source

Thrown at scripts/check-runtime-contract-budget.py:222

def validate_identity_structure(document: dict[str, Any], kind: str) -> None:
    profile = required_value(document, ("tool_catalog", "surface_profile"), kind)
    if profile != TOOL_SURFACE_PROFILE:
        raise RuntimeContractError(
            f"{kind} tool surface_profile must be `{TOOL_SURFACE_PROFILE}`, "
            f"got {profile!r}"
        )

    for mode, _label in VISIBLE_MODES:
        for surface, _surface_label in TOOL_SURFACES:
            base = ("tool_catalog", "modes", mode, surface)
            names = required_value(document, (*base, "tool_names"), kind)
            dotted_names = ".".join((*base, "tool_names"))
            if (
                not isinstance(names, list)
                or any(not isinstance(name, str) or not name for name in names)
                or names != sorted(set(names))
            ):
                raise RuntimeContractError(
                    f"{kind} field `{dotted_names}` must be sorted unique non-empty strings"
                )
            count = metric_value(document, (*base, "tools"), kind)
            if count != len(names):
                raise RuntimeContractError(
                    f"{kind} metric `{'.'.join((*base, 'tools'))}` must equal the "
                    f"owned tool_names length ({len(names)})"
                )
            digest = required_value(document, (*base, "identity_sha256"), kind)
            expected = tool_identity_digest(names)
            if digest != expected:
                raise RuntimeContractError(
                    f"{kind} field `{'.'.join((*base, 'identity_sha256'))}` must "
                    "match the owned sorted tool_names"
                )

    for stage, _label in REPRESENTATIVE_STAGES:
        path = ("representative_context", "stages", stage, "identity_sha256")

View on GitHub (pinned to 8880682c63)

Solutions

  1. Sort and deduplicate the exact tool_names array named in the message (tool_catalog.modes.<mode>.<surface>.tool_names)
  2. Recompute identity_sha256 for that node, since the digest is taken over the sorted names (sha256 of NUL-joined list)
  3. 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)
  4. Once compare() passes again, lock in the state with python3 scripts/check-runtime-contract-budget.py --update

Example fix

// before (scripts/runtime-contract-budget.json)
"tool_names": ["read", "bash", "apply_patch"]

// after - sorted, unique, non-empty
"tool_names": ["apply_patch", "bash", "read"]
Defensive patterns

Strategy: validation

Validate before calling

def tool_names_wellformed(doc: dict) -> bool:
    for mode in ("plan", "act", "operate"):
        for surface in ("full", "active"):
            node = (
                doc.get("tool_catalog", {})
                .get("modes", {})
                .get(mode, {})
                .get(surface, {})
            )
            names = node.get("tool_names")
            if not isinstance(names, list):
                return False
            if any(not isinstance(n, str) or not n for n in names):
                return False
            if names != sorted(set(names)):
                return False
    return True

# before compare(receipt, budget) or run_measurement()
assert tool_names_wellformed(receipt) and tool_names_wellformed(budget)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/d988d5b824e8d25e. Report an issue: GitHub.