Hmbown/CodeWhale · error · RuntimeContractError

{source} document_kind must be `{expected_kind}`, got {actua

Error message

{source} document_kind must be `{expected_kind}`, got {actual_kind!r}

What it means

validate_document compared the document_kind discriminator and it does not match the expected receipt or budget kind string (`codewhale.runtime_contract_receipt` vs `codewhale.runtime_contract_budget`). The discriminator prevents passing the wrong file into the wrong slot and rejects receipts produced by other gates (for example the persistence-backlog script). A missing document_kind surfaces as None in the message.

Source

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

def load_json(path: Path, kind: str) -> dict[str, Any]:
    try:
        document = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError as error:
        raise RuntimeContractError(f"missing {kind}: {path}") from error
    except (OSError, json.JSONDecodeError) as error:
        raise RuntimeContractError(f"invalid {kind} {path}: {error}") from error
    if not isinstance(document, dict):
        raise RuntimeContractError(f"invalid {kind} {path}: top level must be an object")
    return document


def validate_document(
    document: dict[str, Any], expected_kind: str, source: str
) -> None:
    actual_kind = document.get("document_kind")
    if actual_kind != expected_kind:
        raise RuntimeContractError(
            f"{source} document_kind must be `{expected_kind}`, got {actual_kind!r}"
        )
    version = document.get("schema_version")
    if (
        isinstance(version, bool)
        or not isinstance(version, int)
        or version != SCHEMA_VERSION
    ):
        raise RuntimeContractError(
            f"{source} schema_version must be {SCHEMA_VERSION}, got {version!r}"
        )


def required_value(document: dict[str, Any], path: MetricPath, kind: str) -> Any:
    value: Any = document
    dotted = ".".join(path)
    for part in path:
        if not isinstance(value, dict) or part not in value:

View on GitHub (pinned to 8880682c63)

Solutions

  1. Confirm which file went where: --receipt wants document_kind `codewhale.runtime_contract_receipt`, the budget wants `codewhale.runtime_contract_budget`
  2. Regenerate receipts with scripts/measure-runtime-contract.py so the discriminator is written for you
  3. Add the correct document_kind key when hand-building test fixtures

Example fix

# before
python3 scripts/check-runtime-contract-budget.py --receipt scripts/runtime-contract-budget.json
# after
python3 scripts/check-runtime-contract-budget.py --receipt scripts/runtime-contract-receipt.json
Defensive patterns

Strategy: validation

Validate before calling

expected = 'codewhale.runtime_contract_receipt' if is_receipt else 'codewhale.runtime_contract_budget'
assert doc.get('document_kind') == expected, doc.get('document_kind')

Type guard

def is_receipt(doc):
    return isinstance(doc, dict) and doc.get('document_kind') == 'codewhale.runtime_contract_receipt'

Prevention

When it happens

Trigger: Passing the budget file to --receipt; passing a persistence-backlog receipt; hand-building a receipt without the document_kind key; swapping the --receipt and --budget arguments.

Common situations: Copy-pasted CI commands; repositories hosting several receipt formats side by side.

Related errors


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