JuliusBrussee/caveman · error · CatalogError

: dropped or replaced a source from its immutable snapshot…

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

Snapshot files catalog/{verified}.yaml are immutable price-dated records. A row's sources set in current.yaml must be a superset of the sources recorded in its snapshot: existing (pricing) citations may be added to but never silently dropped or replaced while verified_at stays put. The check mirrors the Go isSubset check in catalog_test.go and reports both sets so the diff is obvious.

Solutions

  1. Restore the missing source(s) listed in the snapshot set to the row's sources in current.yaml
  2. If the source genuinely must be replaced, bump verified_at and mint a new catalog/{date}.yaml snapshot containing the new sources
  3. Re-run the sync tool if it is responsible for mangling the sources list
  4. Re-run validate_catalog to confirm snapshot_sources is now a subset of the current sources

Example fix

# before (current.yaml)
sources:
  - https://new-pricing-page.example.com
# after (restored snapshot source)
sources:
  - https://official-pricing.example.com
  - https://new-pricing-page.example.com
Defensive patterns

Strategy: validation

Validate before calling

def sources_preserved(snapshot_row, current_row):
    snap = set(snapshot_row.get("sources") or [])
    return snap.issubset(set(current_row["sources"]))

Prevention

When it happens

Trigger: Running validate_catalog after removing or swapping a source URL from a row's sources list while verified_at is unchanged; the snapshot's sources set fails issubset against the current row's sources and the error prints snapshot vs current sorted source lists.

Common situations: Cleaning up 'stale' citation URLs that were actually part of the audited snapshot; a sync tool rewrote the sources list in a different order or format; branch merge dropped a citation added at verification time; replacing a primary source link with a 'better' one without re-verifying prices.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/ee5c5ee67dd9bf10. Report an issue: GitHub.

Appendix: 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 3ee70a1026)