test(tui-gateway): pin resume overrides to the session profile with two real homes
Replace the scope-probe tests (they stubbed _profile_build_scope and asserted the call sequence, a change-detector on the wiring) with two invariants on real config homes: a secondary profile's stored custom:<name> provider survives deferred and cold resume under a launch profile whose config names the same endpoint differently, and a launch-profile row still resolves against the launch config. Red on origin/main (both paths healed 'custom:local-vllm' to the launch profile's 'custom:local-code'). Co-authored-by: Yagna <yagna@nousresearch.com>
This commit is contained in:
@@ -1,97 +1,93 @@
|
||||
# session.resume must resolve stored runtime overrides under the launch-profile scope.
|
||||
#
|
||||
# _stored_session_runtime_overrides heals a stale persisted provider via the
|
||||
# configured provider chain (canonical_custom_identity / is_routable_provider),
|
||||
# which reads the CURRENT profile config. The eager resume path already wraps
|
||||
# that call in _profile_build_scope; the deferred and cold paths must do the
|
||||
# same or the heal resolves against the ambient launch environment and
|
||||
# mis-routes the resumed chat. See #115607.
|
||||
"""``session.resume`` resolves a stored session's provider overrides under the SESSION profile's scope.
|
||||
|
||||
Under a multiplexed gateway launched as profile A, resuming profile B's stored session used to
|
||||
evaluate B's persisted ``custom:<name>`` provider against A's config: the deferred (Desktop
|
||||
``defer_history``) and cold paths called ``_stored_session_runtime_overrides`` outside
|
||||
``_profile_build_scope(ctx.profile_home)``, so a provider defined only in B was "unroutable",
|
||||
got healed to A's entry for the same endpoint, and the build in B died with
|
||||
``Unknown provider 'custom:local-code'`` (#115607). Two real homes, A→B: B's ``providers.*``
|
||||
entry must survive resume; a launch-profile row keeps resolving against the launch config.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import types
|
||||
import pytest
|
||||
|
||||
from hermes_state import SessionDB
|
||||
from tui_gateway import server
|
||||
|
||||
|
||||
def _install_scope_probe(monkeypatch):
|
||||
# Stub _profile_build_scope as a recording context manager; return call log.
|
||||
calls = {'scope_homes': [], 'overrides_in_scope': []}
|
||||
active = {'home': None}
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _scope(profile_home):
|
||||
calls['scope_homes'].append(profile_home)
|
||||
prev = active['home']
|
||||
active['home'] = profile_home
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
active['home'] = prev
|
||||
|
||||
def _overrides(found):
|
||||
calls['overrides_in_scope'].append(active['home'])
|
||||
return {}
|
||||
|
||||
monkeypatch.setattr(server, '_profile_build_scope', _scope)
|
||||
monkeypatch.setattr(server, '_stored_session_runtime_overrides', _overrides)
|
||||
monkeypatch.setattr(server, '_schedule_resume_hydration', lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, '_schedule_session_cap_enforcement', lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, '_schedule_agent_build', lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, '_resume_response', lambda *a, **k: {'ok': True})
|
||||
return calls
|
||||
_BASE_URL = "http://127.0.0.1:59999/v1"
|
||||
_STORED = "20260919-000000-abcd"
|
||||
|
||||
|
||||
def _deferred_ctx(home):
|
||||
return types.SimpleNamespace(
|
||||
found={'id': 's1'},
|
||||
profile_home=str(home),
|
||||
target='s1',
|
||||
db=None,
|
||||
owns_db=False,
|
||||
omit_messages=False,
|
||||
mint=lambda prompts=True: ('sid-d', 'tui', '/tmp'),
|
||||
record=lambda source, cwd, history, overrides=None, **kw: {},
|
||||
claim=lambda sid, record: None,
|
||||
info=lambda cwd, overrides: {},
|
||||
)
|
||||
class _NoopHydration:
|
||||
def __call__(self, *_a, **_k):
|
||||
return None
|
||||
|
||||
|
||||
def _cold_ctx(home):
|
||||
return types.SimpleNamespace(
|
||||
found={'id': 's1'},
|
||||
profile_home=str(home),
|
||||
target='s1',
|
||||
rid='1',
|
||||
mint=lambda prompts=True: ('sid-c', 'tui', '/tmp'),
|
||||
restore=lambda: ([], [], []),
|
||||
display_prefix=lambda: [],
|
||||
record=lambda source, cwd, history, overrides=None, **kw: {},
|
||||
claim=lambda sid, record: None,
|
||||
info=lambda cwd, overrides: {},
|
||||
)
|
||||
def _write_home(home, config_text: str) -> None:
|
||||
home.mkdir(parents=True)
|
||||
(home / "config.yaml").write_text(config_text)
|
||||
(home / ".env").write_text("")
|
||||
|
||||
|
||||
def test_deferred_resume_resolves_overrides_under_profile_scope(monkeypatch, tmp_path):
|
||||
home = tmp_path / 'work'
|
||||
home.mkdir()
|
||||
calls = _install_scope_probe(monkeypatch)
|
||||
ctx = _deferred_ctx(home)
|
||||
|
||||
server._resume_deferred(ctx)
|
||||
|
||||
assert calls['scope_homes'] == [str(home)]
|
||||
assert calls['overrides_in_scope'] == [str(home)]
|
||||
def _store(home, provider: str) -> None:
|
||||
db = SessionDB(db_path=home / "state.db")
|
||||
try:
|
||||
db.create_session(_STORED, "desktop", model="local-code",
|
||||
model_config={"model": "local-code", "provider": provider, "base_url": _BASE_URL})
|
||||
db.append_message(_STORED, "user", "hi")
|
||||
db.append_message(_STORED, "assistant", "hello")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_cold_resume_resolves_overrides_under_profile_scope(monkeypatch, tmp_path):
|
||||
home = tmp_path / 'work'
|
||||
home.mkdir()
|
||||
calls = _install_scope_probe(monkeypatch)
|
||||
ctx = _cold_ctx(home)
|
||||
@pytest.fixture()
|
||||
def homes(monkeypatch, tmp_path):
|
||||
"""Launch home A owns ``custom_providers: Local-Code``; secondary B owns ``providers.local-vllm`` — the
|
||||
same endpoint under a different name, so a cross-profile heal is observable."""
|
||||
launch, secondary = tmp_path / "a", tmp_path / "b"
|
||||
_write_home(launch, "model:\n default: gpt-4o\n provider: openai\n"
|
||||
f"custom_providers:\n - name: Local-Code\n base_url: {_BASE_URL}\n api_key: k-a\n")
|
||||
_write_home(secondary, "model:\n default: local-code\n provider: custom:local-vllm\n"
|
||||
f"providers:\n local-vllm:\n api: {_BASE_URL}\n api_key: k-b\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(launch))
|
||||
monkeypatch.setattr(server, "_hermes_home", str(launch))
|
||||
monkeypatch.setattr(server, "_profile_home", lambda p: secondary if p == "b" else None)
|
||||
monkeypatch.setattr(server, "_get_db", lambda: SessionDB(db_path=launch / "state.db"))
|
||||
monkeypatch.setattr(server, "_enable_gateway_prompts", lambda: None)
|
||||
monkeypatch.setattr(server, "_schedule_resume_hydration", _NoopHydration())
|
||||
monkeypatch.setattr(server, "_schedule_agent_build", lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, "_schedule_session_cap_enforcement", lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, "_maybe_schedule_auto_continue", lambda *a, **k: None)
|
||||
monkeypatch.setattr(server, "_default_session_cwd", lambda *a, **k: str(tmp_path))
|
||||
known = set(server._sessions)
|
||||
yield {"a": launch, "b": secondary}
|
||||
with server._sessions_lock:
|
||||
for sid in [s for s in server._sessions if s not in known]:
|
||||
server._sessions.pop(sid, None)
|
||||
|
||||
server._resume_cold(ctx)
|
||||
|
||||
assert calls['scope_homes'] == [str(home)]
|
||||
assert calls['overrides_in_scope'] == [str(home)]
|
||||
def _resume(**params):
|
||||
resp = server.handle_request({"id": "1", "method": "session.resume",
|
||||
"params": {"session_id": _STORED, "source": "desktop", **params}})
|
||||
assert "error" not in resp, resp
|
||||
return server._sessions[resp["result"]["session_id"]]["resume_runtime_overrides"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", [{"defer_history": True, "omit_messages": True}, {}], ids=["deferred", "cold"])
|
||||
def test_secondary_profile_overrides_resolve_against_its_own_config(homes, path):
|
||||
_store(homes["b"], "custom:local-vllm")
|
||||
|
||||
overrides = _resume(profile="b", **path)
|
||||
|
||||
assert overrides["provider_override"] == "custom:local-vllm"
|
||||
assert overrides["model_override"]["provider"] == "custom:local-vllm"
|
||||
|
||||
|
||||
def test_launch_profile_overrides_still_resolve_against_launch_config(homes):
|
||||
_store(homes["a"], "custom:local-code")
|
||||
|
||||
overrides = _resume(defer_history=True, omit_messages=True)
|
||||
|
||||
assert overrides["provider_override"] == "custom:local-code"
|
||||
|
||||
Reference in New Issue
Block a user