Files
hermes-agent/tests/hermes_cli/test_models_detect_vendor_ownership.py
Teknium ee35a4624f fix(routing): address review — session runtime decides, quarantine continues the chain, unclassified ids break ownership
Three defects found in review of the first head (@ehz0ah):

* The mid-request hop read the PERSISTED provider from disk. A live
  `/model xai-oauth` session over `model.provider: auto` therefore still
  reached the discovery chain and billed Nous. _try_payment_fallback now
  takes the route's main_runtime snapshot; disk is the fallback only when
  no session runtime exists.

* After a configured fallback was quarantined mid-request (401, refresh
  failed), the second pass went straight to the discovery chain, which
  the new gate refuses — so later CONFIGURED entries never ran and the
  original error was re-raised. The second pass now re-walks the task
  chain and main chain (the quarantined entry is unhealthy and skipped)
  before discovery.

* current_provider_owns_vendor dropped ids detect_vendor could not
  classify, so Bedrock's 15-id catalog (14 unclassified `us.anthropic…`)
  looked exclusively DeepSeek and `/model deepseek-v4-pro` stuck on
  Bedrock. An unclassified id now counts as evidence of a multi-vendor
  catalog: ownership requires every id to classify to the one vendor.
2026-09-10 09:11:00 -07:00

40 lines
1.8 KiB
Python

"""A first-party session never re-routes its own vendor's model when the live catalog cannot vouch.
Second half of the Astra incident (#97487): the live-catalog guard only helps when the fetch
succeeds. A transient Codex outage, or a static fallback that lags an early-access rollout, left
``/model gpt-6-astra`` on ``openai-codex`` walking the ladder to OpenRouter (which relists every
vendor) and silently rebuilding the session on a metered aggregator.
"""
from __future__ import annotations
import pytest
from hermes_cli import models, models_detect
@pytest.fixture
def ladder_would_hijack(monkeypatch):
"""Live catalog unavailable; OpenRouter lists everything; the user holds an OpenRouter key."""
monkeypatch.setattr(models, "cached_provider_model_ids", lambda provider, **_: [])
monkeypatch.setattr(models, "_find_openrouter_slug", lambda name: f"vendor/{name}")
monkeypatch.setattr(models_detect, "provider_has_credentials", lambda p: p == "openrouter")
@pytest.mark.parametrize("provider,model", [
("openai-codex", "gpt-6-astra"),
("xai-oauth", "grok-5-preview"),
("anthropic", "claude-opus-5-early"),
])
def test_own_vendor_id_stays_when_live_catalog_is_empty(ladder_would_hijack, provider, model):
assert models.detect_provider_for_model(model, provider) is None
@pytest.mark.parametrize("provider,model", [
("openai-codex", "claude-opus-4.7"), # other vendor's id on a single-vendor provider
("bedrock", "deepseek-v4-pro"), # multi-vendor catalog whose non-deepseek ids the
# classifier cannot place: never "exclusively deepseek"
])
def test_non_owned_id_still_remaps_to_keyed_aggregator(ladder_would_hijack, provider, model):
assert models.detect_provider_for_model(model, provider) == ("openrouter", f"vendor/{model}")