JuliusBrussee/caveman · error · CatalogError

{label}: pricing changed without a new verified_at snapshot

Error message

{label}: pricing changed without a new verified_at snapshot version (checked catalog/{verified}.yaml)

What it means

Raised by validate_catalog() when a current.yaml row's pricing identity (provider, model, region, currency, pricing, verified_at, plus price-affecting capabilities) has no byte-semantic match in the immutable dated snapshot catalog/<verified_at>.yaml. The snapshot is the tamper-evidence for the price an earlier verified_at already attested — if pricing changed while verified_at stayed put, the change ships under an attestation nobody made, so validation refuses it.

Source

Thrown at shared/provider-catalog/validate_catalog.py:234

    for index, row in enumerate(current):
        label = f"current.yaml row {index + 1}"
        identity = validate_row(row, label, check_time)
        if identity in seen:
            raise CatalogError(f"{label}: duplicate identity {'/'.join(identity)}")
        seen.add(identity)

        verified = parsed_time(row["verified_at"], label).date().isoformat()
        snapshot = snapshots.setdefault(
            verified,
            load_yaml(CATALOG_DIR / f"{verified}.yaml"),
        )
        identity = pricing_identity(row)
        matched = next(
            (snap_row for snap_row in snapshot if pricing_identity(snap_row) == identity),
            None,
        )
        if matched is None:
            raise CatalogError(
                f"{label}: pricing changed without a new verified_at snapshot version (checked catalog/{verified}.yaml)"
            )
        # sources is excluded from pricing_identity so a capability citation can
        # be added without minting a new price-dated snapshot, but a PRICING
        # citation must never be silently swapped or dropped while verified_at
        # stays put. Mirrors the Go isSubset check in catalog_test.go.
        snapshot_sources = set(matched.get("sources") or [])
        if not snapshot_sources.issubset(set(row["sources"])):
            raise CatalogError(
                f"{label}: dropped or replaced a source from its immutable snapshot "
                f"catalog/{verified}.yaml without a new verified_at "
                f"(snapshot had {sorted(snapshot_sources)}, current has {sorted(row['sources'])})"
            )


def main() -> int:
    try:
        validate_catalog()

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. If the price genuinely changed: bump verified_at to the day you re-checked it, and create (or extend) catalog/<that-date>.yaml containing the row with its new pricing — the snapshot must contain a row whose pricing identity matches exactly.
  2. If you bumped verified_at already, verify catalog/<verified_at>.yaml exists and contains the row (load_yaml is called on it lazily; a missing file errors as invalid YAML/missing file).
  3. If the edit was accidental (typo), revert the pricing fields to match the snapshot instead of minting a new one.
  4. Remember capabilities edits that are NOT price-affecting (tools/vision/json_mode/context_window_tokens) do not require a snapshot — only pricing and the three price-affecting capability keys do.

Example fix

# before — price edited in place, verified_at unchanged (no snapshot match)
- provider: openai
  model: gpt-4o
  region: us
  pricing: {input_per_million: 2.75}   # was 2.50 in catalog/2026-06-01.yaml
  verified_at: "2026-06-01T00:00:00Z"

# after — re-verified, new date + new snapshot catalog/2026-08-15.yaml with the same row
- provider: openai
  model: gpt-4o
  region: us
  pricing: {input_per_million: 2.75}
  verified_at: "2026-08-15T00:00:00Z"
Defensive patterns

Strategy: validation

Validate before calling

PRICING_IDENTITY_KEYS = ("provider", "model", "region", "currency", "pricing", "verified_at")
PRICE_AFFECTING_CAPS = ("regional_processing_multiplier", "inference_geo_us_multiplier", "region_agnostic_pricing")

def snapshot_pins_row(current_row: dict, snapshot_rows: list[dict]) -> bool:
    def ident(r):
        caps = {k: r.get("capabilities", {}).get(k) for k in PRICE_AFFECTING_CAPS}
        return tuple(r.get(k) for k in PRICING_IDENTITY_KEYS), tuple(sorted(caps.items()))
    return any(ident(s) == ident(current_row) for s in snapshot_rows)

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "without a new verified_at snapshot" in str(e):
        # bump verified_at to today and copy the row into catalog/<today>.yaml
        mint_snapshot_for_changed_rows()
    raise

Prevention

When it happens

Trigger: Editing any pricing field (or a PriceAffectingCapabilities key like regional_processing_multiplier, inference_geo_us_multiplier, region_agnostic_pricing) without bumping verified_at; bumping verified_at but forgetting to create/update catalog/<new-date>.yaml; fixing a typo in an existing price in place; or reusing an old verified_at date for a changed price.

Common situations: A quick 'just fix the number' edit to current.yaml; a new model added to current.yaml but not copied into a dated snapshot; snapshot files pruned by an over-eager cleanup that deleted a referenced date.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/e0cbce18b1182e2e. Report an issue: GitHub.