JuliusBrussee/caveman · error · CatalogError

{label}: verified_at is in the future

Error message

{label}: verified_at is in the future

What it means

Raised by validate_row() in the provider-catalog validator when a row's verified_at timestamp is strictly later than the validation time (now, default datetime.now(timezone.utc)). verified_at is price provenance — the sole input to catalogVersion() embedded in signed receipts — so a future date would attest a price check that has not happened yet. The comparison happens after parsed_time() normalizes the value to UTC, so both naive-skew and timezone-skew land here.

Source

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

            continue
        if isinstance(value, bool) or not isinstance(value, (int, float)):
            raise CatalogError(f"{label}: pricing.{field} must be numeric or null")
        if not math.isfinite(value) or value < 0:
            raise CatalogError(f"{label}: pricing.{field} must be finite and nonnegative")
        if field == "batch_discount_fraction" and value > 1:
            raise CatalogError(f"{label}: pricing.batch_discount_fraction must be <= 1")

    if not isinstance(row["capabilities"], dict):
        raise CatalogError(f"{label}: capabilities must be an object")
    sources = row["sources"]
    if not isinstance(sources, list) or not sources:
        raise CatalogError(f"{label}: sources must be a non-empty list")
    if any(not isinstance(source, str) or not source.startswith("https://") for source in sources):
        raise CatalogError(f"{label}: every source must be HTTPS")

    verified_at = parsed_time(row["verified_at"], label)
    if verified_at > now:
        raise CatalogError(f"{label}: verified_at is in the future")
    if verified_at < now - timedelta(days=120):
        raise CatalogError(f"{label}: verified_at is older than 120 days")
    return identity[0], identity[1], identity[2]


def check_review_markers(text: str, label: str) -> None:
    """Refuse a catalog that still carries an unreviewed sync proposal.

    YAML comments are invisible to the parsed rows, so this reads the raw file:
    a rubber-stamped merge must not be able to advance price provenance while
    the line saying "nobody has confirmed this yet" is still in the file.
    """
    offending = [
        index + 1
        for index, line in enumerate(text.splitlines())
        if line.lstrip().startswith(REVIEW_MARKER)
    ]
    if offending:

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Set verified_at to the actual UTC date you checked the vendor pricing page (RFC3339 with timezone, e.g. 2026-08-15T00:00:00Z); it must be <= now.
  2. If the timestamp came from scripts/catalog_sync_modelsdev.py, re-check the rows against their cited sources and correct the date before merging.
  3. If this fires in a test, pass a `now` that is at or after the fixture's verified_at (validate_catalog(now=...)).
  4. If the machine clock is wrong (VM, CI runner drift), fix the clock via NTP and re-run; do not bump the date to silence it.

Example fix

# current.yaml (before)
verified_at: "2026-09-01T00:00:00Z"  # dated in the future

# after
verified_at: "2026-08-15T00:00:00Z"  # the day prices were actually checked
Defensive patterns

Strategy: validation

Validate before calling

from datetime import datetime, timezone

def verified_at_is_not_future(verified_at: str) -> bool:
    try:
        ts = datetime.fromisoformat(verified_at.replace("Z", "+00:00"))
    except ValueError:
        return False
    return ts.tzinfo is not None and ts.astimezone(timezone.utc) <= datetime.now(timezone.utc)

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    # message names the row label; fix the row's verified_at, do not catch-and-continue
    raise SystemExit(f"catalog invalid: {e}")

Prevention

When it happens

Trigger: Running validate_catalog.py (or validate_catalog(now=...)) when any row in catalog/current.yaml has verified_at later than the check time: a sync script stamping the proposal date instead of the check date, a hand-edit copying tomorrow's date, a test passing a fixed `now` earlier than the fixture's verified_at, or a machine clock behind the author's clock (timestamp written on a fast clock, validated on a slow one).

Common situations: Running the catalog validator in CI or on a fresh checkout right after an automated catalog sync proposed changes; timezone confusion where a local-time timestamp without offset would fail earlier ('must include a timezone') but a +00:00 future date reaches this check; fixtures in tests using hardcoded dates that drift into the future relative to a frozen `now`.

Related errors


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