claire/tests/test_routing.py
Natalie 16c030c6b3 feat(@projects/@claire): routing resolver for location-transparent Claire
route(signals, fleet) -> RouteDecision via a deterministic cascade:
explicit host > capability-pin (uses hosts_with_capability) > sticky
(subject's session/task already runs on a host, via sessions+assignments)
> default-local. Pure + auditable (reason+candidates surfaced); the LLM
classify step and cross-host execution are separate layers. 13 tests.

Part of task 13764f2f.
(manual commit via ALLOW_COMMIT — autocommit LLM still down on claire)
2026-06-03 01:41:27 -07:00

143 lines
5.2 KiB
Python

"""Routing cascade: explicit > capability > sticky > default-local."""
from __future__ import annotations
import pytest
from uuid import UUID
from claire.config import ClaireConfig, HostEntry
from claire.routing import route
from claire.web import service
@pytest.fixture
def cfg() -> ClaireConfig:
return ClaireConfig(
machine_id="m",
this_host="plum",
known_hosts=[
HostEntry(name="plum", aliases=["local"]),
HostEntry(name="apricot", capabilities=["cores:64", "gpu"]),
HostEntry(name="black", capabilities=["media", "transmission"]),
],
)
def _add_session(conn, uuid: str, host: str) -> None:
conn.execute(
"INSERT INTO sessions (uuid, host, updated_hlc) VALUES (?, ?, ?)",
(uuid, host, "1"),
)
def _task_with_worker(conn, gen, *, project: str, host: str, session_uuid: str):
"""Create a project+task and an active assignment to a session on `host`.
Returns the task id (str). Uses service so FK constraints are satisfied."""
service.create_project(conn, gen, name=project)
task = service.add_task(conn, gen, project=project, title="t")
_add_session(conn, session_uuid, host)
service.create_assignment(conn, gen, task_id=task.id, session_uuid=UUID(session_uuid))
return str(task.id)
# 1. explicit -----------------------------------------------------------------
def test_explicit_host_wins(conn, cfg) -> None:
d = route(conn, cfg, receiving_host="plum", explicit_host="apricot")
assert (d.host, d.reason) == ("apricot", "explicit")
def test_explicit_alias_resolves(conn, cfg) -> None:
# "local" → plum even when received on plum
d = route(conn, cfg, receiving_host="plum", explicit_host="local")
assert (d.host, d.reason) == ("plum", "explicit")
def test_explicit_unknown_host_falls_back_local_not_silent(conn, cfg) -> None:
d = route(conn, cfg, receiving_host="plum", explicit_host="mars")
assert d.host == "plum"
assert d.reason == "unknown-host"
# 2. capability ---------------------------------------------------------------
def test_capability_single(conn, cfg) -> None:
d = route(conn, cfg, receiving_host="plum", capability_needs=["media"])
assert (d.host, d.reason) == ("black", "capability")
def test_capability_key_prefix(conn, cfg) -> None:
# asking "cores" matches "cores:64"
d = route(conn, cfg, receiving_host="plum", capability_needs=["cores"])
assert d.host == "apricot"
def test_capability_intersection_of_needs(conn, cfg) -> None:
# gpu AND cores → only apricot has both; media-only black excluded
d = route(conn, cfg, receiving_host="plum", capability_needs=["gpu", "cores"])
assert d.host == "apricot"
def test_capability_no_match_falls_through_to_default(conn, cfg) -> None:
d = route(conn, cfg, receiving_host="plum", capability_needs=["fpga"])
assert (d.host, d.reason) == ("plum", "default-local")
def test_capability_tiebreak_least_loaded(conn) -> None:
cfg = ClaireConfig(
machine_id="m", this_host="plum",
known_hosts=[
HostEntry(name="a", capabilities=["media"]),
HostEntry(name="b", capabilities=["media"]),
],
)
d = route(None, cfg, receiving_host="plum", capability_needs=["media"],
host_load={"a": 5, "b": 1})
assert d.host == "b"
assert set(d.candidates) == {"a", "b"}
# 3. sticky -------------------------------------------------------------------
def test_sticky_by_session(conn, cfg) -> None:
_add_session(conn, "11111111-1111-1111-1111-111111111111", "apricot")
d = route(conn, cfg, receiving_host="plum",
session_uuid="11111111-1111-1111-1111-111111111111")
assert (d.host, d.reason) == ("apricot", "sticky")
def test_sticky_by_task_via_active_assignment(conn, gen, cfg) -> None:
task_id = _task_with_worker(
conn, gen, project="p", host="black",
session_uuid="22222222-2222-2222-2222-222222222222",
)
d = route(conn, cfg, receiving_host="plum", task_id=task_id)
assert (d.host, d.reason) == ("black", "sticky")
def test_session_reference_beats_task(conn, gen, cfg) -> None:
_add_session(conn, "33333333-3333-3333-3333-333333333333", "apricot")
task_id = _task_with_worker(
conn, gen, project="p", host="black",
session_uuid="44444444-4444-4444-4444-444444444444",
)
d = route(conn, cfg, receiving_host="plum",
session_uuid="33333333-3333-3333-3333-333333333333", task_id=task_id)
assert d.host == "apricot" # session wins
# 4. default ------------------------------------------------------------------
def test_default_local_when_no_signal(conn, cfg) -> None:
d = route(conn, cfg, receiving_host="apricot")
assert (d.host, d.reason) == ("apricot", "default-local")
def test_precedence_explicit_over_everything(conn, cfg) -> None:
# a sticky session on black, capability=media (black), but explicit apricot wins
_add_session(conn, "55555555-5555-5555-5555-555555555555", "black")
d = route(conn, cfg, receiving_host="plum", explicit_host="apricot",
capability_needs=["media"],
session_uuid="55555555-5555-5555-5555-555555555555")
assert (d.host, d.reason) == ("apricot", "explicit")