HUX-06: the auto-router now consults the loopback HUX service (worker trust, HMAC-derived conversation id from the persisted session) for the conversation's selected friendly mode and maps it onto the existing provider-neutral pools: fast->auto/fast, thoughtful/research->auto/deep, create->auto/balanced; private pins the local route and refuses hosted overrides. Explicit UI picks keep precedence; every HUX absence or failure falls through to the previous behaviour unchanged. 95% branch coverage; quality contract registers the new module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
277 lines
10 KiB
Python
277 lines
10 KiB
Python
"""HUX-06 adoption: the stored friendly mode shapes the next route boundary."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
ROOT = Path(__file__).parents[2]
|
|
PLUGIN = ROOT / "services/hermes/plugins/auto-router"
|
|
sys.path.insert(0, str(PLUGIN.parent))
|
|
|
|
SPEC = importlib.util.spec_from_file_location("hermes_hux_mode", PLUGIN / "hux_mode.py")
|
|
assert SPEC and SPEC.loader
|
|
hux_mode = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[SPEC.name] = hux_mode
|
|
SPEC.loader.exec_module(hux_mode)
|
|
|
|
ROUTER_SPEC = importlib.util.spec_from_file_location(
|
|
"hermes_auto_router_hux", PLUGIN / "__init__.py"
|
|
)
|
|
assert ROUTER_SPEC and ROUTER_SPEC.loader
|
|
router = importlib.util.module_from_spec(ROUTER_SPEC)
|
|
sys.modules[ROUTER_SPEC.name] = router
|
|
ROUTER_SPEC.loader.exec_module(router)
|
|
|
|
KEY = bytes(range(32))
|
|
SUBJECT = "usr_" + "ab" * 32
|
|
SLOT = "slot-3"
|
|
|
|
|
|
def _files(tmp_path: Path) -> dict[str, str]:
|
|
for name in ("context-key", "subject", "worker-key"):
|
|
existing = tmp_path / name
|
|
if existing.exists():
|
|
existing.chmod(0o600)
|
|
existing.unlink()
|
|
context = tmp_path / "context-key"
|
|
context.write_bytes(KEY)
|
|
context.chmod(0o600)
|
|
subject = tmp_path / "subject"
|
|
subject.write_text(SUBJECT + "\n")
|
|
subject.chmod(0o400)
|
|
worker = tmp_path / "worker-key"
|
|
worker.write_text("worker-key-value\n")
|
|
worker.chmod(0o400)
|
|
return {
|
|
"HUX_BASE_URL": "http://127.0.0.1:8790",
|
|
"HUX_TENANT_SLOT": SLOT,
|
|
"HUX_CONTEXT_KEY_FILE": str(context),
|
|
"HUX_SUBJECT_FILE": str(subject),
|
|
"HUX_WORKER_KEY_FILE": str(worker),
|
|
"HUX_PROJECT_SOURCE": "profile:default",
|
|
}
|
|
|
|
|
|
def _record(mode: str, override: str | None = None) -> dict:
|
|
switchyard: dict = {"route_id": "atlas/auto/fast"}
|
|
if override:
|
|
switchyard["override_route_id"] = override
|
|
return {"mode": {"mode": mode, "switchyard": switchyard}}
|
|
|
|
|
|
def _fetch_for(record: dict, status: int = 200, seen: dict | None = None):
|
|
def fetch(url: str, headers: dict) -> tuple[int, dict]:
|
|
if seen is not None:
|
|
seen["url"] = url
|
|
seen["headers"] = headers
|
|
seen["calls"] = seen.get("calls", 0) + 1
|
|
return status, record
|
|
return fetch
|
|
|
|
|
|
def _selection(tmp_path, record, session="sess-1", status=200, seen=None):
|
|
hux_mode._cache.clear()
|
|
return hux_mode.selection(
|
|
session, _files(tmp_path), _fetch_for(record, status, seen)
|
|
)
|
|
|
|
|
|
def test_every_friendly_mode_maps_to_a_provider_neutral_pool(tmp_path):
|
|
assert _selection(tmp_path, _record("fast")) == ("atlas/auto/fast", "")
|
|
assert _selection(tmp_path, _record("thoughtful")) == ("atlas/auto/deep", "")
|
|
assert _selection(tmp_path, _record("research")) == ("atlas/auto/deep", "")
|
|
assert _selection(tmp_path, _record("create")) == ("atlas/auto/balanced", "")
|
|
for route in ("atlas/auto/fast", "atlas/auto/deep", "atlas/auto/balanced"):
|
|
assert route in router.AUTO_ROUTES # both providers stay eligible
|
|
|
|
|
|
def test_private_mode_is_local_only(tmp_path):
|
|
assert _selection(tmp_path, _record("private")) == (
|
|
"atlas/manual/local/qwen-14b", "",
|
|
)
|
|
# A hosted override can never escape private mode.
|
|
assert _selection(
|
|
tmp_path, _record("private", "atlas/manual/claude/opus")
|
|
) is None
|
|
assert _selection(
|
|
tmp_path, _record("private", "atlas/manual/local/qwen-14b")
|
|
) == ("atlas/manual/local/qwen-14b", "")
|
|
|
|
|
|
def test_advanced_override_is_honoured_for_hosted_modes(tmp_path):
|
|
assert _selection(
|
|
tmp_path, _record("create", "atlas/manual/claude/sonnet")
|
|
) == ("atlas/manual/claude/sonnet", "")
|
|
# A tampered override never breaks routing: the neutral pool wins.
|
|
assert _selection(tmp_path, _record("create", "not-a-route")) == (
|
|
"atlas/auto/balanced", "",
|
|
)
|
|
|
|
|
|
def test_identity_and_derivation_reach_the_service_exactly(tmp_path):
|
|
seen: dict = {}
|
|
_selection(tmp_path, _record("fast"), seen=seen)
|
|
conv = "conv_" + hmac.new(
|
|
KEY,
|
|
"\0".join(
|
|
("hux.context.id.v1", "conversation", SLOT, SUBJECT, "sess-1")
|
|
).encode(),
|
|
hashlib.sha256,
|
|
).hexdigest()[:32]
|
|
prj = "prj_" + hmac.new(
|
|
KEY,
|
|
"\0".join(
|
|
("hux.context.id.v1", "project", SLOT, SUBJECT, "profile:default")
|
|
).encode(),
|
|
hashlib.sha256,
|
|
).hexdigest()[:32]
|
|
assert seen["url"] == (
|
|
f"http://127.0.0.1:8790/hux/v1/projects/{prj}/conversations/{conv}/mode"
|
|
)
|
|
assert seen["headers"]["X-Hux-Trust"] == "worker"
|
|
assert seen["headers"]["X-Hux-Surface"] == "worker"
|
|
assert seen["headers"]["X-Hux-Subject"] == SUBJECT
|
|
assert seen["headers"]["X-Hux-Relay-Key"] == "worker-key-value"
|
|
|
|
|
|
def test_every_failure_is_fail_open(tmp_path):
|
|
assert hux_mode.selection("", _files(tmp_path), _fetch_for(_record("fast"))) is None
|
|
assert _selection(tmp_path, _record("fast"), status=404) is None
|
|
assert _selection(tmp_path, _record("unknown")) is None
|
|
assert _selection(tmp_path, {"mode": "not-a-dict"}) is None
|
|
env = _files(tmp_path)
|
|
env["HUX_TENANT_SLOT"] = "slot-nope"
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
env = _files(tmp_path)
|
|
env["HUX_BASE_URL"] = "http://10.0.0.1:8790"
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
env = _files(tmp_path)
|
|
Path(env["HUX_CONTEXT_KEY_FILE"]).write_bytes(b"short")
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
|
|
def broken(url, headers):
|
|
raise OSError("down")
|
|
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", _files(tmp_path), broken) is None
|
|
|
|
|
|
def test_secret_file_guards_reject_links_and_bad_shapes(tmp_path):
|
|
env = _files(tmp_path)
|
|
target = tmp_path / "elsewhere"
|
|
target.write_bytes(KEY)
|
|
link = tmp_path / "context-link"
|
|
link.symlink_to(target)
|
|
env["HUX_CONTEXT_KEY_FILE"] = str(link)
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
env = _files(tmp_path)
|
|
env["HUX_SUBJECT_FILE"] = str(tmp_path / "does-not-exist")
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
env = _files(tmp_path)
|
|
Path(env["HUX_SUBJECT_FILE"]).chmod(0o600)
|
|
Path(env["HUX_SUBJECT_FILE"]).write_text("not-a-subject\n")
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
env = _files(tmp_path)
|
|
Path(env["HUX_WORKER_KEY_FILE"]).chmod(0o600)
|
|
Path(env["HUX_WORKER_KEY_FILE"]).write_text("\n")
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("s", env, _fetch_for(_record("fast"))) is None
|
|
# A malformed raw session id never reaches HMAC derivation.
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("bad session", _files(tmp_path), _fetch_for(_record("fast"))) is None
|
|
|
|
|
|
def test_real_fetch_round_trip_and_refusals(tmp_path):
|
|
import http.server
|
|
import threading
|
|
|
|
record = _record("fast")
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
def do_GET(self): # noqa: N802 - stdlib naming
|
|
body = json.dumps(record).encode()
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, *args): # noqa: D102
|
|
return
|
|
|
|
server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
env = _files(tmp_path)
|
|
env["HUX_BASE_URL"] = f"http://127.0.0.1:{server.server_port}"
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("sess-live", env) == ("atlas/auto/fast", "")
|
|
finally:
|
|
server.shutdown()
|
|
thread.join(timeout=5)
|
|
# A dead loopback port fails open through the real fetch path too.
|
|
env = _files(tmp_path)
|
|
env["HUX_BASE_URL"] = f"http://127.0.0.1:{server.server_port}"
|
|
hux_mode._cache.clear()
|
|
assert hux_mode.selection("sess-dead", env) is None
|
|
|
|
|
|
def test_cache_is_bounded(tmp_path):
|
|
env = _files(tmp_path)
|
|
hux_mode._cache.clear()
|
|
for index in range(514):
|
|
hux_mode.selection(f"sess-{index}", env, _fetch_for(_record("fast")))
|
|
assert len(hux_mode._cache) <= 513
|
|
|
|
|
|
def test_selection_is_cached_per_session(tmp_path):
|
|
seen: dict = {}
|
|
env = _files(tmp_path)
|
|
hux_mode._cache.clear()
|
|
first = hux_mode.selection("sess-c", env, _fetch_for(_record("fast"), seen=seen))
|
|
second = hux_mode.selection("sess-c", env, _fetch_for(_record("fast"), seen=seen))
|
|
assert first == second == ("atlas/auto/fast", "")
|
|
assert seen["calls"] == 1
|
|
|
|
|
|
def test_boundary_prefers_ui_pick_and_adopts_hux_mode(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(router, "POLICY_PATH", tmp_path / "route-policy.json")
|
|
monkeypatch.setattr(
|
|
router, "_hux_mode_selection", lambda session: ("atlas/auto/deep", "")
|
|
)
|
|
agent = SimpleNamespace(
|
|
_hermes_explicit_model_pick=False,
|
|
_hermes_explicit_reasoning_effort="",
|
|
_hermes_routing_priority="",
|
|
)
|
|
assert router._boundary_selection(agent, "sess-1") == (
|
|
"atlas/auto/deep", "", "hux-mode",
|
|
)
|
|
# No session means no adoption and the existing default is untouched.
|
|
assert router._boundary_selection(agent, "")[2] == "auto"
|
|
# An explicit UI pick always outranks the stored mode.
|
|
picked = SimpleNamespace(
|
|
_hermes_explicit_model_pick=True,
|
|
model="atlas/manual/claude/opus",
|
|
_hermes_explicit_reasoning_effort="high",
|
|
_hermes_routing_priority="",
|
|
)
|
|
assert router._boundary_selection(picked, "sess-1") == (
|
|
"atlas/manual/claude/opus", "high", "ui-manual",
|
|
)
|
|
monkeypatch.setattr(router, "_hux_mode_selection", lambda session: None)
|
|
assert router._boundary_selection(agent, "sess-1")[2] == "auto"
|