2026-05-08-anthropic-catalog-ingestor

2026-05-08 — Anthropic catalog ingestor (first concrete ModelIngestor)

Summary

Closed the multi-round "0 ingestors registered" gap that surfaced in R17, R18, and R19. The IngestorScheduler scaffolding has existed since R15; what was missing was a concrete ModelIngestor impl. This entry adds the first one — Anthropic's /v1/models — registered hourly when ANTHROPIC_API_KEY is set.

Ships paired with the mTLS strict-mode cutover runbook (R5 in the R19 risk register) so the runbook is documented with rollback signals before anyone flips the flag.

Lockstep checklist

  • [x] Sourcesrc/router/intelligence/anthropic-ingestor.ts (115 lines) implements ModelIngestor, polls Anthropic /v1/models with x-api-key + anthropic-version: 2023-06-01, returns liveness + displayName + createdAt patches. Pricing intentionally NOT supplied (Anthropic's models endpoint omits it; provider-catalog-pricing.ts remains the curated source).
  • [x] Testssrc/router/intelligence/anthropic-ingestor.test.ts (6 regression tests): liveness patch shape, no-API-key skip, non-2xx HTTP error path, network throw, no-pricing assertion, key-rotation safety (resolver called fresh each cycle).
  • [x] Wiringsrc/router/model-router-init.ts registers the ingestor with 60 60 1000 ms (hourly) when process.env.ANTHROPIC_API_KEY is set. Logs Anthropic catalog ingestor registered (hourly) on bootstrap; logs the disabled state otherwise.
  • [x] SDK — None needed. ModelIngestor is internal infrastructure; no API surface change.
  • [x] MCP — None needed. Same reason.
  • [x] Runbookdocs/runbooks/mtls-strict-cutover.md: gates strict-mode cutover on zero advisory bypasses over 24h, documents Option A (config redeploy) + Option B (ChangeSet+restart), and a rollback procedure tied to legitimate-caller 403 detection.
  • [x] Ship log — this file.

Why this matters

R17/R18/R19 each flagged "0 ingestors registered" as drift surface against LiteLLM's 100+ auto-discovered catalog. The static catalog (provider-catalog-pricing.ts) is hand-maintained and silently goes stale when Anthropic adds/removes a model. The ingestor doesn't replace pricing curation; it just makes model existence drift detectable through the deprecation detector and intelligence store.

Hourly is conservative. The Anthropic /v1/models payload is small; rate limits are not a concern at this cadence. We can drop to 15-minute or 5-minute intervals after observing real drift cadence in production.

Caller-id discipline

The ingestor's getApiKey: () => string | undefined is called fresh on every cycle, so key rotation propagates without process restart. Verified by anthropic-ingestor.test.ts:rotation-safety.

Verification

Local:

pnpm tsgo            # 0 errors
pnpm lint            # 0 warnings, 0 errors
pnpm test:fast -- src/router/intelligence/anthropic-ingestor.test.ts   # 6/6 pass
pnpm check           # All gates pass

Production:

After deploy, the bootstrap log line will confirm registration:

Anthropic catalog ingestor registered (hourly)

If ANTHROPIC_API_KEY is missing in production env, the alternate path logs:

Anthropic catalog ingestor not registered: ANTHROPIC_API_KEY not set

The ingestor's run output appears in IngestorScheduler logs as anthropic ingest: N models reported alive once an hour.

Follow-ups (not in this PR)

  • OpenAI /v1/models ingestor (same shape, easier — OpenAI returns just IDs)
  • Google models.list ingestor (different auth, different shape)
  • DeepSeek, x.ai, Groq, Perplexity, Moonshot ingestors
  • Wire ingestor output to the deprecation detector so model removal triggers an alert (currently the patches just update the intelligence store)
  • Drop to 15-min cadence after one week of hourly observation