Hmbown/CodeWhale · error · PersistenceBacklogError

budget schema_version changed

Error message

budget schema_version changed

What it means

validate_budget rejects a budget whose schema_version does not equal SCHEMA_VERSION (2). The checker enforces a one-way schema: an old budget written against schema version 1 (or a future one) is not interpretable under the current contract. This prevents silently comparing a current receipt against a stale budget layout.

Solutions

  1. Update the budget's schema_version to 2 and regenerate its ceilings/baseline_observation per the new schema
  2. Re-run the measurement flow that produces the budget for the current schema
  3. Restore the budget file matching the checked-out script version from git

Example fix

// before
{ "schema_version": 1, ... }
// after
{ "schema_version": 2, ... }
Defensive patterns

Strategy: validation

Validate before calling

budget = json.load(open("scripts/persistence-backlog-budget.json"))
assert budget.get("schema_version") == 2, f"budget schema is {budget.get('schema_version')}, script requires 2"

Type guard

def is_current_schema(doc):
    return isinstance(doc, dict) and doc.get("schema_version") == 2

Try / catch

try:
    validate_budget(budget)
except PersistenceBacklogError as e:
    if "schema_version changed" in str(e):
        print("regenerate the budget for the current schema before checking")
    raise

Prevention

When it happens

Trigger: compare calls validate_budget on scripts/persistence-backlog-budget.json (or a --budget override) whose schema_version field is absent or not 2, e.g. after the script bumped SCHEMA_VERSION without regenerating the committed budget.

Common situations: Pulling new code with an updated schema while an old local budget file remains; editing the budget by hand and dropping the field; cherry-picking a script change without its budget regeneration commit.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/2acbeefa1c0e8b57. Report an issue: GitHub.

Appendix: source

Thrown at scripts/check-persistence-backlog-budget.py:246

            non_negative_integer(receipt[field], field)
        before = receipt["rss_before_bytes"]
        if receipt["rss_during_delta_bytes"] != max(
            0, receipt["rss_during_bytes"] - before
        ):
            raise PersistenceBacklogError("rss_during_delta_bytes is inconsistent")
        if receipt["rss_after_delta_bytes"] != max(
            0, receipt["rss_after_bytes"] - before
        ):
            raise PersistenceBacklogError("rss_after_delta_bytes is inconsistent")
    elif any(receipt[field] is not None for field in rss_fields):
        raise PersistenceBacklogError("unsupported RSS fields must be null")


def validate_budget(budget: dict[str, Any]) -> None:
    if budget.get("document_kind") != BUDGET_KIND:
        raise PersistenceBacklogError(f"budget document_kind must be {BUDGET_KIND}")
    if budget.get("schema_version") != SCHEMA_VERSION:
        raise PersistenceBacklogError("budget schema_version changed")
    fixture = budget.get("fixture")
    if not isinstance(fixture, dict) or set(fixture) != set(FIXTURE):
        raise PersistenceBacklogError("budget fixture no longer matches the frozen workload")
    for field, expected in FIXTURE.items():
        if type(fixture[field]) is not type(expected) or fixture[field] != expected:
            raise PersistenceBacklogError(
                f"budget fixture.{field} must remain {expected!r}"
            )
    if budget.get("baseline_receipt") != BASELINE_RECEIPT_REFERENCE:
        raise PersistenceBacklogError("budget baseline_receipt path changed")
    ceilings = budget.get("ceilings")
    baseline = budget.get("baseline_observation")
    if not isinstance(ceilings, dict) or not isinstance(baseline, dict):
        raise PersistenceBacklogError("budget needs ceilings and baseline_observation objects")
    for field in CEILING_FIELDS:
        ceiling = non_negative_integer(ceilings.get(field), f"ceilings.{field}")
        observed = non_negative_integer(
            baseline.get(field), f"baseline_observation.{field}"

View on GitHub (pinned to 433685b202)