170 lines
7.3 KiB
Python

"""HUX-06 provider-neutral friendly modes and scoped route selection.
Automatic modes express intent to Switchyard and never name a provider. An
exact route can only be selected through the explicit advanced path and must
exist in the operator-provided route catalog. Private mode is local-only.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
from typing import Any
from hux import contracts, rules
from hux.errors import Conflict, Invalid, NotFound
from hux.http import Request, Response, Router, page
from hux.store import now_iso
CARD = "HUX-06"
FAMILY = "mode_selections"
MODE_NAMES = ("fast", "thoughtful", "research", "create", "private")
MANUAL_ROUTE = re.compile(r"^atlas/manual/(codex|claude|local)/[a-z0-9][a-z0-9/-]{0,100}$")
MAX_CATALOG_ROUTES = 256
SCHEMAS = contracts.load_all()
def _body(request: Request) -> dict[str, Any]:
if not isinstance(request.body, dict):
raise Invalid("body must be a JSON object")
extra = set(request.body) - {"project_id", "mode", "advanced", "override_route_id"}
if extra:
raise Invalid("unexpected mode fields", sorted(extra))
return request.body
def _scope(request: Request, project_id: Any) -> tuple[dict[str, Any], dict[str, Any]]:
"""Resolve a conversation and its mandatory owning project."""
from hux import organization
try:
conversation = request.store.get(organization.CONVERSATIONS, request.params["id"])
except (Invalid, NotFound) as error:
raise NotFound("conversation not found") from error
path_project = request.params.get("project_id")
actual = conversation.get("project_id")
if not isinstance(project_id, str) or project_id != path_project or not actual or project_id != actual:
raise NotFound("project or conversation not found")
try:
project = request.store.get(organization.PROJECTS, project_id)
except (Invalid, NotFound) as error:
raise NotFound("project or conversation not found") from error
return project, conversation
def _record_id(conversation_id: str) -> str:
digest = hashlib.sha256(conversation_id.encode()).hexdigest()[:24]
return f"mode_{digest}"
def _catalog() -> set[str]:
raw = os.environ.get("HUX_SWITCHYARD_ROUTE_CATALOG", "")
items = [item.strip() for item in raw.split(",") if item.strip()]
if len(items) > MAX_CATALOG_ROUTES:
return set()
return {item for item in items if MANUAL_ROUTE.fullmatch(item)}
def _contract(mode: str, advanced: bool, override: Any) -> dict[str, Any]:
if not isinstance(mode, str) or mode not in MODE_NAMES:
raise Invalid("unknown friendly mode")
if override is not None:
if not advanced or not isinstance(override, str) or not MANUAL_ROUTE.fullmatch(override):
raise Invalid("an exact route requires a valid advanced manual route")
if override not in _catalog():
raise Invalid("advanced route is not in the Switchyard catalog")
if mode == "private" and not override.startswith("atlas/manual/local/"):
raise Invalid("private mode is local-only")
elif advanced:
raise Invalid("advanced selection requires override_route_id")
try:
result = rules.mode_contract(mode, override)
except ValueError as error:
raise Invalid(str(error)) from error
problems = contracts.validate("mode.schema.json", result, SCHEMAS)
if problems:
raise Invalid("mode failed contract validation", problems)
if mode != "private" and result["switchyard"]["route_id"].startswith("atlas/manual/"):
raise Invalid("automatic modes may not pin a provider")
return result
def _fingerprint(body: dict[str, Any]) -> str:
allowed = {key: body.get(key) for key in ("project_id", "mode", "advanced", "override_route_id")}
return hashlib.sha256(json.dumps(allowed, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def _replay(request: Request, key: str, fingerprint: str) -> dict[str, Any] | None:
for row in request.store.read(FAMILY, "idempotency"):
if row.get("key") != key:
continue
if row.get("fingerprint") != fingerprint:
raise Conflict("Idempotency-Key was already used for a different selection")
return request.store.get(FAMILY, row["id"])
return None
def list_modes(request: Request) -> Response:
"""Return all provider-neutral mode contracts."""
items = [_contract(name, False, None) for name in MODE_NAMES]
request.audit("modes.list", "modes")
return page(items)
def get_selection(request: Request) -> Response:
"""Return the selected mode for one bound conversation."""
project_id = request.params["project_id"]
_, conversation = _scope(request, project_id)
record = request.store.get(FAMILY, _record_id(conversation["id"]))
request.audit("modes.read", record["id"])
return Response(200, record, {"ETag": str(record["revision"]), "Cache-Control": "no-store"})
def put_selection(request: Request) -> Response:
"""Select a mode using If-Match and Idempotency-Key."""
body = _body(request)
_, conversation = _scope(request, body.get("project_id"))
key = request.idempotency_key()
if not key:
raise Invalid("Idempotency-Key is required")
expected = request.if_match()
if expected is None:
raise Invalid("If-Match is required; use 0 for the first selection")
fingerprint = _fingerprint(body)
mode = _contract(body.get("mode"), body.get("advanced") is True, body.get("override_route_id"))
record_id = _record_id(conversation["id"])
with request.store.lock(FAMILY):
replayed = _replay(request, key, fingerprint)
if replayed is not None:
request.audit("modes.select", replayed["id"], reason="idempotent_replay")
return Response(200, replayed, {"ETag": str(replayed["revision"]), "HUX-Replayed": "true", "Cache-Control": "no-store"})
exists = request.store.exists(FAMILY, record_id)
if expected != (request.store.get(FAMILY, record_id)["revision"] if exists else 0):
raise Conflict("mode selection revision does not match If-Match")
stamp = now_iso()
record = {
"id": record_id,
"schema": "hux.mode_selection.v1",
"owner": request.identity.subject,
"project_id": body["project_id"],
"conversation_id": conversation["id"],
"mode": mode,
"updated_at": stamp,
}
stored = request.store.put(FAMILY, record, expected_revision=expected)
request.store.append(FAMILY, "idempotency", {"key": key, "fingerprint": fingerprint, "id": record_id, "at": stamp})
with request.store.lock("conversations"):
current = request.store.get("conversations", conversation["id"])
request.store.put("conversations", {**current, "mode": mode["mode"], "updated_at": stamp}, current["revision"])
request.audit("modes.select", record_id)
return Response(200, stored, {"ETag": str(stored["revision"]), "Cache-Control": "no-store"})
def register(router: Router) -> None:
"""Attach HUX-06 routes."""
router.add("GET", "/hux/v1/modes", CARD, "modes.list", list_modes)
router.add("GET", "/hux/v1/projects/{project_id}/conversations/{id}/mode", CARD, "modes.read", get_selection)
router.add("PUT", "/hux/v1/projects/{project_id}/conversations/{id}/mode", CARD, "modes.select", put_selection)