Files
hermes-webui/tests/test_issue7168_round6_default_isolated_clamp.py
T
alfred-rootson b71c88f292 fix(workspace): preserve remote POSIX paths across Session & streaming callsites (#7131) (#7168)
Closes the remote POSIX workspace path preservation gap across Session and streaming. Profile-aware runtime path resolution.

Co-authored-by: alfred-rootson <alfred.wojtasordzik@gmail.com>
2026-09-15 15:37:11 -07:00

295 lines
14 KiB
Python

"""Re-gate round 6 (#7168): the logical "default" name must honor isolated-mode clamping.
Maintainer round-6 re-gate CORE (one remaining instance of the isolation
class): ``_resolve_profile_home_param("default")`` short-circuited to
``_DEFAULT_HERMES_HOME`` BEFORE delegating to
``api.profiles.get_hermes_home_for_profile()``, so it never reached the
isolated-mode clamp in ``_resolve_profile_home_for_name`` (which pins every
lookup to ``_INITIAL_HERMES_HOME`` when ``HERMES_WEBUI_ISOLATED_PROFILE`` is
enabled). In an isolated deployment pinned at ``<base>/profiles/default``, a
session created with ``profile="default"`` therefore used the BASE root
home's workspace + config instead of the pinned one.
Fix under test: the literal shortcut is gone — the logical string ``"default"``
flows through ``get_hermes_home_for_profile()`` like every other id, so the
clamp applies. Literal-default routing to the global state files is RETAINED
(canonical ``_is_default_profile_home`` identity), asserted by the fallback
tests below.
"""
import pytest
import api.workspace as workspace
from api import profiles
@pytest.fixture
def isolated_default_pinned(tmp_path, monkeypatch):
"""Isolated-mode deployment pinned at <base>/profiles/default.
Mirrors the gate reproduction: HERMES_WEBUI_ISOLATED_PROFILE=1 with a
profile-shaped _INITIAL_HERMES_HOME whose directory NAME is literally
'default' (the exact shape where base and pin diverge). Base home gets a
DISTINCT config/workspace marker so any leak of the root home is visible.
"""
base = tmp_path / ".hermes"
pinned = base / "profiles" / "default"
pinned.mkdir(parents=True)
(pinned / "webui_state").mkdir()
# Distinct base-vs-pinned configs: only the PINNED one carries the marker.
(pinned / "config.yaml").write_text(
"workspace: /srv/pinned-workspace\n", encoding="utf-8"
)
monkeypatch.setenv("HERMES_WEBUI_ISOLATED_PROFILE", "1")
monkeypatch.setattr(profiles, "_INITIAL_ISOLATED_PROFILE_OPT_IN", "1")
monkeypatch.setattr(profiles, "_INITIAL_HERMES_HOME", str(pinned))
monkeypatch.setattr(profiles, "_DEFAULT_HERMES_HOME", base)
monkeypatch.setattr(profiles, "_LIST_PROFILES_CACHE", None)
# Hermetic w.r.t. the runner's real state dir / remote terminal config.
monkeypatch.setattr(workspace, "_remote_terminal_cwd", lambda profile=None: None)
return {"base": base, "pinned": pinned, "tmp": tmp_path}
class TestRound6DefaultHonorsIsolationClamp:
"""The logical "default" name resolves through the clamped delegated path."""
def test_resolver_pins_default_in_isolated_mode(self, isolated_default_pinned):
env = isolated_default_pinned
assert profiles._is_isolated_profile_mode() is True
assert (
profiles.get_hermes_home_for_profile("default") == env["pinned"]
) # pre-existing correct behavior (the clamp)
got = workspace._resolve_profile_home_param("default")
assert got == env["pinned"].resolve(), (
f"_resolve_profile_home_param('default') must reach the isolated-mode "
f"clamp and return the PINNED home {env['pinned']}, got {got}"
)
assert got != env["base"], "must not resolve to the base/root home"
def test_get_last_workspace_uses_global_state_not_base(self, isolated_default_pinned, monkeypatch):
"""Round 7 contract: 'default' STATE lives in the GLOBAL files only.
Round-6 follow-up: once the resolver pins config/path resolution at
the isolated home, the canonical-home check in _profile_state_dir
split explicit profile="default" state I/O onto {pinned}/webui_state/
while ambient calls kept the global dir. Round 7 restores ONE state
authority: literal "default" reads/writes the global files, so this
test now asserts the global binding WINS and the BASE home's
webui_state is never consulted.
"""
env = isolated_default_pinned
# The authoritative GLOBAL binding...
global_ws = env["tmp"] / "srv" / "global-ws"
global_ws.mkdir(parents=True)
global_lw = env["tmp"] / "global-state"
global_lw.mkdir(parents=True)
# Both global state files must move TOGETHER: in production
# _GLOBAL_WS_FILE and _GLOBAL_LW_FILE share one state dir.
monkeypatch.setattr(workspace, "_GLOBAL_WS_FILE", global_lw / "workspaces.json")
monkeypatch.setattr(workspace, "_GLOBAL_LW_FILE", global_lw / "last_workspace.txt")
(global_lw / "last_workspace.txt").write_text(str(global_ws), encoding="utf-8")
# ...and a POISONED base binding that must NOT win. Point the base
# home's own webui_state at a foreign path to prove no base read.
poisoned_ws = env["tmp"] / "srv" / "poisoned-base-ws"
poisoned_ws.mkdir(parents=True)
base_state = env["base"] / "webui_state"
base_state.mkdir(parents=True)
(base_state / "last_workspace.txt").write_text(str(poisoned_ws), encoding="utf-8")
got = workspace.get_last_workspace(profile="default")
assert str(got) == str(global_ws.resolve()), (
f"profile='default' in isolated mode must read the GLOBAL state "
f"authority ({global_lw / 'last_workspace.txt'}), got {got!r}"
)
def test_get_last_workspace_global_fallback_only_when_canonically_default(
self, isolated_default_pinned, monkeypatch
):
"""No profile-local state + non-pinned layout → legacy global fallback.
Literal-default routing to _GLOBAL_LW_FILE is retained OUTSIDE isolated
mode: when the resolved default home IS canonically the default home,
get_last_workspace(profile="default") still reads the global file
(historical behavior, re-gate round 3 contract).
"""
env = isolated_default_pinned
# Disable isolation so 'default' resolves canonically to the base home.
monkeypatch.delenv("HERMES_WEBUI_ISOLATED_PROFILE", raising=False)
monkeypatch.setattr(profiles, "_INITIAL_ISOLATED_PROFILE_OPT_IN", "")
monkeypatch.setattr(profiles, "_INITIAL_HERMES_HOME", str(env["base"]))
global_lw = env["tmp"] / "global-state"
global_lw.mkdir(parents=True)
monkeypatch.setattr(workspace, "_GLOBAL_LW_FILE", global_lw / "last_workspace.txt")
shared_ws = env["tmp"] / "srv" / "shared-ws"
shared_ws.mkdir(parents=True)
(global_lw / "last_workspace.txt").write_text(str(shared_ws), encoding="utf-8")
# The conftest autouse cleanup fixture writes TEST_STATE_DIR /
# last_workspace.txt at every teardown; for a canonically-default
# profile that IS the profile-local tier and would shadow the global
# file under test. Pin the profile-local tier to an absent path so the
# GLOBAL fallback is what actually gets exercised.
monkeypatch.setattr(
workspace,
"_last_workspace_file_for_profile",
lambda p=None: env["tmp"] / "absent-webui_state" / "last_workspace.txt",
)
got = workspace.get_last_workspace(profile="default")
assert str(got) == str(shared_ws), (
"outside isolated mode, profile='default' keeps the legacy global "
"fallback (canonical identity retained after the round-6 fix)"
)
def test_new_session_binds_global_state_workspace(self, isolated_default_pinned, monkeypatch):
"""Session creation with profile='default' honors the global state
authority and never leaks onto the base home (round-7 contract)."""
import api.models as models
env = isolated_default_pinned
bound_ws = env["tmp"] / "srv" / "bound-session-ws"
bound_ws.mkdir(parents=True)
global_lw = env["tmp"] / "global-state"
global_lw.mkdir(parents=True)
# Both global state files must move TOGETHER: in production
# _GLOBAL_WS_FILE and _GLOBAL_LW_FILE share one state dir.
monkeypatch.setattr(workspace, "_GLOBAL_WS_FILE", global_lw / "workspaces.json")
monkeypatch.setattr(workspace, "_GLOBAL_LW_FILE", global_lw / "last_workspace.txt")
(global_lw / "last_workspace.txt").write_text(str(bound_ws), encoding="utf-8")
# POISONED base-home binding must not win.
poisoned_ws = env["tmp"] / "srv" / "poisoned-base-ws"
poisoned_ws.mkdir(parents=True)
base_state = env["base"] / "webui_state"
base_state.mkdir(parents=True)
(base_state / "last_workspace.txt").write_text(str(poisoned_ws), encoding="utf-8")
s = models.new_session(profile="default")
assert s.profile == "default"
assert str(s.workspace) == str(bound_ws.resolve()), (
f"new_session(profile='default') in isolated mode must bind the "
f"GLOBAL state authority's workspace, got {s.workspace!r}"
)
assert str(s.workspace) != str(poisoned_ws.resolve()), (
"must never leak onto the BASE home's webui_state binding"
)
def test_session_config_reads_pinned_config(
self, isolated_default_pinned, monkeypatch
):
"""Config for the resolved 'default' home comes from the PINNED home.
In isolated mode the pinned home IS the active home, so
get_config_for_profile_home takes the ambient-match branch — which is
exactly the authority we must verify: the ambient resolver (and its
HERMES_CONFIG_PATH) must be anchored at the PINNED home, never at the
base/root one. Point the authoritative config INSIDE the pinned home;
had the resolver leaked to the base home, this config would not win.
"""
from api import config as cfg_mod
from api.config import get_config_for_profile_home
env = isolated_default_pinned
monkeypatch.setenv("HERMES_CONFIG_PATH", str(env["pinned"] / "config.yaml"))
cfg_mod.reload_config()
home = workspace._resolve_profile_home_param("default")
assert home == env["pinned"].resolve()
got = get_config_for_profile_home(home)
assert got.get("workspace") == "/srv/pinned-workspace", (
"config for the resolved 'default' home must be anchored at the "
"PINNED profile's home/config, never the base root's"
)
# The BASE root has no config.yaml — a leak onto the base home would
# have produced the defaults-only dict (no 'workspace' key).
class TestRound7DefaultStateRouting:
"""Explicit and ambient "default" STATE I/O share ONE authority (round 7).
Round-6 CORE follow-up: routing the literal "default" name through the
delegated resolver made the canonical-home check in
``_profile_state_dir`` send EXPLICIT ``profile="default"`` state
reads/writes to ``{pinned}/webui_state/`` while AMBIENT calls kept using
the global state dir — splitting saved workspaces between two
authorities and hiding the pre-upgrade list. Fix under test:
literal-"default" STATE routing stays on the global files, while config
and workspace PATH resolution keep the pinned home.
"""
def _seed_global_state(self, env, monkeypatch):
"""Point both global state files at an isolated tmp dir."""
g = env["tmp"] / "global-state"
g.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(workspace, "_GLOBAL_WS_FILE", g / "workspaces.json")
monkeypatch.setattr(workspace, "_GLOBAL_LW_FILE", g / "last_workspace.txt")
return g
def test_explicit_default_state_dir_is_global(self, isolated_default_pinned, monkeypatch):
env = isolated_default_pinned
g = self._seed_global_state(env, monkeypatch)
assert workspace._profile_state_dir(profile="default") == g, (
"explicit profile='default' must route STATE to the global dir "
"(same authority as ambient), not {pinned}/webui_state/"
)
assert workspace._workspaces_file("default") == g / "workspaces.json"
assert workspace._last_workspace_file("default") == g / "last_workspace.txt"
def test_explicit_and_ambient_share_workspaces_authority(
self, isolated_default_pinned, monkeypatch
):
import json
env = isolated_default_pinned
g = self._seed_global_state(env, monkeypatch)
# Pre-upgrade saved list lives ONLY in the global file.
pre_dir = env["tmp"] / "srv" / "pre-upgrade"
pre_dir.mkdir(parents=True)
pre = [{"path": str(pre_dir), "name": "Pre"}]
(g / "workspaces.json").write_text(
json.dumps(pre, ensure_ascii=False), encoding="utf-8"
)
# Explicit profile="default" reads the pre-upgrade list...
assert workspace.load_workspaces(profile="default") == pre
# ...and explicit writes land in the SAME global file.
added = env["tmp"] / "srv" / "added"
added.mkdir(parents=True)
updated = pre + [{"path": str(added), "name": "Added"}]
workspace.save_workspaces(updated, profile="default")
assert json.loads((g / "workspaces.json").read_text(encoding="utf-8")) == updated
# Ambient calls see the identical authority — no split.
assert workspace._profile_state_dir(None) == g
assert workspace.load_workspaces() == updated
# No divergent webui_state authority may appear under the pinned home.
assert not (env["pinned"] / "webui_state" / "workspaces.json").exists()
assert not (env["pinned"] / "webui_state" / "last_workspace.txt").exists()
def test_last_workspace_round_trip_shared_explicit_ambient(
self, isolated_default_pinned, monkeypatch
):
env = isolated_default_pinned
g = self._seed_global_state(env, monkeypatch)
ws = env["tmp"] / "srv" / "bound-ws"
ws.mkdir(parents=True)
workspace.set_last_workspace(str(ws), profile="default")
assert (g / "last_workspace.txt").exists(), (
"set_last_workspace('default') must write the GLOBAL file"
)
assert not (env["pinned"] / "webui_state" / "last_workspace.txt").exists()
assert workspace.get_last_workspace(profile="default") == str(ws)
# Ambient (isolated mode's active profile is literally "default")
# resolves to the same binding.
assert workspace.get_last_workspace() == str(ws)