From 8e32aa09d48d2b9e44b605facf3cd80531e762fe Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Fri, 29 May 2026 13:05:58 -0700 Subject: [PATCH] fix(server): advertise registry.default when model.default_alias is foreign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /v1/api/models blanked default_alias whenever model.default_alias named an alias absent from the server's live registry — e.g. when a standalone turnstone-server shares a ConfigStore with a console whose model.default_alias points at a console-only / DB alias (or the underlying model id rather than the alias). The interactive dashboard then showed a bare "Default model" placeholder even though a new workstream launches on a concrete model. Mirror session_factory's _effective_default_alias / _effective_routing: fall back to registry.default (which already incorporates a *valid* model.default_alias override) when the configured alias is unset or foreign, blanking only if registry.default is itself unresolvable. The endpoint now reports the model creation actually uses. --- tests/test_server_available_models.py | 28 ++++++++++++++++++++++++--- turnstone/server.py | 13 ++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/test_server_available_models.py b/tests/test_server_available_models.py index 65f6fc0e..958adca3 100644 --- a/tests/test_server_available_models.py +++ b/tests/test_server_available_models.py @@ -5,8 +5,10 @@ composer + the channel gateway. The dashboard's Options panel renders each one as a "Default — alias (model)" placeholder, so the resolution chain has to stay honest: -* ``default_alias`` ← ``model.default_alias``, falling back to the - registry default; blanked when it points at a disabled/removed alias. +* ``default_alias`` ← ``model.default_alias`` when it names an enabled + alias, otherwise ``registry.default`` (mirrors session_factory's + ``_effective_default_alias`` — the model a new workstream actually launches + on). Only blanked when even ``registry.default`` is unresolvable. * ``channel_default_alias`` ← ``channels.default_model_alias``. * ``judge_default_alias`` ← ``judge.model``, but *only* when it names an enabled alias. An unset / whitespace / unknown / disabled value stays @@ -151,10 +153,30 @@ def test_model_default_falls_back_to_registry_default() -> None: assert body["judge_default_alias"] == "" -def test_model_default_blanked_when_pointing_at_unknown_alias() -> None: +def test_model_default_foreign_alias_falls_back_to_registry_default() -> None: + """``model.default_alias`` naming an alias absent from THIS server's + registry (e.g. a console-only alias leaking through a shared ConfigStore) + falls back to ``registry.default`` — the model creation actually uses — + rather than being blanked. Blanking made the dashboard show a bare + "Default model" placeholder even though a workstream would launch on a + concrete model.""" body = _get_models( _make_client( aliases={"primary": "vendor/primary"}, + registry_default="primary", + settings={"model.default_alias": "ghost"}, + ) + ) + assert body["default_alias"] == "primary" + + +def test_model_default_blanks_only_when_registry_default_also_unresolvable() -> None: + """The defensive blank still applies when neither the configured alias + nor ``registry.default`` resolves to an enabled alias.""" + body = _get_models( + _make_client( + aliases={"primary": "vendor/primary"}, + registry_default="", settings={"model.default_alias": "ghost"}, ) ) diff --git a/turnstone/server.py b/turnstone/server.py index 24234e6b..a5693301 100644 --- a/turnstone/server.py +++ b/turnstone/server.py @@ -1170,10 +1170,17 @@ async def list_available_models(request: Request) -> JSONResponse: default_alias = cs.get("model.default_alias") or "" channel_default_alias = cs.get("channels.default_model_alias") or "" judge_default_alias = (cs.get("judge.model") or "").strip() - if not default_alias: - default_alias = registry.default - # Clear defaults that point to unknown/disabled aliases. + # Mirror session_factory's ``_effective_default_alias`` (and + # ``_effective_routing``): a ``model.default_alias`` that's unset — or + # names an alias absent from THIS server's registry — falls back to + # ``registry.default``, which is the model a new workstream actually + # launches on. Reporting "" here made the dashboard show a bare + # "Default model" placeholder even though creation has a concrete default + # (e.g. when the shared ConfigStore points at a console-only alias). enabled_aliases = set(registry.list_aliases()) + if default_alias not in enabled_aliases: + default_alias = registry.default + # Defensive: only blank if even registry.default is unknown/disabled. if default_alias and default_alias not in enabled_aliases: default_alias = "" if channel_default_alias and channel_default_alias not in enabled_aliases: