JuliusBrussee/caveman · error · CatalogError

{label}: dropped or replaced a source from its immutable sna

Error message

{label}: dropped or replaced a source from its immutable snapshot catalog/{verified}.yaml without a new verified_at (snapshot had {sorted(snapshot_sources)}, current has {sorted(row['sources'])})

What it means

Raised by validate_catalog() when the matched snapshot row's sources are not a subset of the current row's sources. sources is deliberately excluded from pricing_identity so a capability citation can be added without minting a price-dated snapshot — but a PRICING source must never be silently swapped or dropped while verified_at stays put, because the citation is how the attested price gets audited. Mirrors the Go isSubset check in catalog_test.go.

Source

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

            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()
    except CatalogError as error:
        print(f"provider catalog invalid: {error}", file=sys.stderr)
        return 1
    print("provider catalog valid: current rows are fresh, sourced, and snapshot-backed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. If a pricing source must change, ADD the new URL alongside the old one (superset is allowed) rather than replacing it.
  2. If the old source truly must go (dead link, wrong page), treat it as a price re-check: confirm the price against the new source, bump verified_at, and mint catalog/<new-date>.yaml.
  3. Restore the dropped URL(s) listed in the message's 'snapshot had' set if the removal was unintentional.

Example fix

# before — snapshot had https://openai.com/pricing, current replaced it
sources: ["https://platform.openai.com/docs/pricing"]

# after — superset keeps the pinned citation
sources: ["https://openai.com/pricing", "https://platform.openai.com/docs/pricing"]
Defensive patterns

Strategy: validation

Validate before calling

def sources_cover_snapshot(current_sources: list[str], snapshot_sources: list[str]) -> bool:
    return set(snapshot_sources).issubset(set(current_sources))

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "dropped or replaced a source" in str(e):
        # re-add the pinned URLs from the snapshot's set, or re-verify + mint snapshot
        restore_or_reattest(e)
    raise

Prevention

When it happens

Trigger: Removing or replacing a URL in a row's `sources` list without bumping verified_at and minting a new snapshot — e.g. swapping a dead pricing-page URL for a new one, deduplicating sources, or a sync script rewriting the list. The message shows both sorted sets (snapshot had X, current has Y) so the dropped/replaced entry is visible.

Common situations: Link-rot cleanup PRs that 'fix broken URLs' across the catalog; tools that normalize or sort source lists and drop entries; a re-verification that replaces rather than supplements the old citation.

Related errors


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