JuliusBrussee/caveman · error · CatalogError

: unknown field(s)

Error message

{label}: unknown field(s): {', '.join(sorted(unknown))}

What it means

A catalog row contains a top-level field not in the allowed schema (REQUIRED_KEYS plus the optional capabilities_verified_at). The validator is strict-allowlist: unknown fields are typos, drifted schema, or unvetted data, so they fail the build rather than being silently ignored. Thrown from validate_row.

Solutions

  1. Remove or rename the listed unknown field(s) to the schema's snake_case names
  2. If the data belongs in the row, move it into the allowed `capabilities` mapping or a YAML comment
  3. If a new top-level field is genuinely needed, add it to TOP_LEVEL_KEYS/REQUIRED_KEYS in validate_catalog.py and to schemas/provider-catalog.schema.json in the same change
  4. Check the row against an existing valid row in current.yaml for exact field names

Example fix

# before
- provider: openai
  verifiedAt: "2026-07-05T00:00:00Z"

# after
- provider: openai
  verified_at: "2026-07-05T00:00:00Z"
Defensive patterns

Strategy: validation

Validate before calling

TOP_LEVEL_KEYS = {"provider","model","region","currency","pricing","capabilities","sources","verified_at","capabilities_verified_at"}

def no_unknown_fields(row: dict) -> bool:
    return not (set(row) - TOP_LEVEL_KEYS)

Type guard

def has_known_keys_only(row: dict) -> bool:
    return set(row).issubset(TOP_LEVEL_KEYS)

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "unknown field(s)" in str(e):
        rename_or_remove_fields(str(e))

Prevention

When it happens

Trigger: validate_row sees `set(row) - TOP_LEVEL_KEYS` non-empty, e.g. a row with `verifiedAt` (camelCase), `verification_date`, or a newly invented key like `notes` placed at row level instead of inside capabilities.

Common situations: Renaming a field without updating the schema; camelCase vs snake_case mismatch from a JS-authored script; adding free-form commentary at row level instead of as a YAML comment; schema additions made in one file but not the other.

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/cb1a36ec74072bed. Report an issue: GitHub.

Appendix: source

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

    if isinstance(value, datetime):
        parsed = value
    elif isinstance(value, str):
        try:
            parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
        except ValueError as error:
            raise CatalogError(f"{label}: verified_at must be RFC3339") from error
    else:
        raise CatalogError(f"{label}: verified_at must be an RFC3339 timestamp")
    if parsed.tzinfo is None:
        raise CatalogError(f"{label}: verified_at must include a timezone")
    return parsed.astimezone(timezone.utc)


def validate_row(row: dict[str, Any], label: str, now: datetime) -> tuple[str, str, str]:
    unknown = set(row) - TOP_LEVEL_KEYS
    missing = REQUIRED_KEYS - set(row)
    if unknown:
        raise CatalogError(f"{label}: unknown field(s): {', '.join(sorted(unknown))}")
    if missing:
        raise CatalogError(f"{label}: missing field(s): {', '.join(sorted(missing))}")

    identity: list[str] = []
    for field in ("provider", "model", "region"):
        value = row[field]
        if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
            raise CatalogError(f"{label}: {field} must be a safe non-empty identifier")
        identity.append(value)
    if row["currency"] != "USD":
        raise CatalogError(f"{label}: currency must be USD")

    pricing = row["pricing"]
    if not isinstance(pricing, dict):
        raise CatalogError(f"{label}: pricing must be an object")
    pricing_unknown = set(pricing) - PRICING_KEYS
    if pricing_unknown:
        raise CatalogError(f"{label}: unknown pricing field(s): {', '.join(sorted(pricing_unknown))}")

View on GitHub (pinned to 3ee70a1026)