jenkins d1225bcab4 hermes(chat): read-only Atlas cluster visibility for chat
RBAC: the built-in view ClusterRole (which never includes Secrets, so
Vault-managed material stays structurally invisible) plus a read-only
extra for nodes, namespaces, PVs, storage classes, CRDs, Flux resources
and metrics, bound to the chat service account. Tooling: a cluster-read
plugin registers a GET-only cluster_read tool against the in-cluster
API using the pod's projected token - secrets paths refused in the
handler as well, malformed segments rejected, responses bounded and
stripped of managedFields noise. Classified read_files/low in the HUX
capability map. RBAC applies on push; the tool activates when the pods
next roll (bundled with the round-3 voice build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 13:33:48 -03:00

73 lines
3.1 KiB
Python

"""Conservative mapping from Hermes tools to HUX capabilities."""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class ToolPolicy:
"""The HUX gate inputs for one tool family."""
capability: str
risk: str
external: bool
EXACT = {
"read_file": ToolPolicy("read_files", "low", False),
"read_many_files": ToolPolicy("read_files", "low", False),
"list_directory": ToolPolicy("read_files", "low", False),
"search_files": ToolPolicy("read_files", "low", False),
"session_search": ToolPolicy("read_files", "low", False),
"tool_search": ToolPolicy("read_files", "low", False),
"write_file": ToolPolicy("write_files", "medium", False),
"patch": ToolPolicy("write_files", "medium", False),
"memory": ToolPolicy("memory_write", "medium", False),
"delegate_task": ToolPolicy("delegate", "high", False),
"terminal": ToolPolicy("shell", "high", True),
# The Python sandbox is isolated by design; it is internal shell work,
# unlike the real terminal above.
"python": ToolPolicy("shell", "medium", False),
"python_sandbox": ToolPolicy("shell", "medium", False),
# Core assistant faculties are reads, not side effects.
"skills": ToolPolicy("read_files", "low", False),
"clarify": ToolPolicy("read_files", "low", False),
"todo": ToolPolicy("read_files", "low", False),
"vision": ToolPolicy("read_files", "low", False),
# Browsing is the product; it is network, not an unknown side effect.
"browser": ToolPolicy("network", "medium", True),
"web": ToolPolicy("network", "medium", True),
"web_search": ToolPolicy("network", "medium", True),
# Image generation produces an artifact through the trusted broker.
"image_gen": ToolPolicy("artifact_write", "medium", False),
# Read-only cluster visibility; RBAC and the handler both exclude secrets.
"cluster_read": ToolPolicy("read_files", "low", False),
}
PREFIXES = (
(("skill_", "skills_", "todo_", "clarify_"), ToolPolicy("read_files", "low", False)),
(("web_", "browser_", "http_", "mcp_"), ToolPolicy("network", "medium", True)),
(("send_", "mail_", "email_", "slack_", "discord_", "telegram_"), ToolPolicy("send_message", "high", True)),
(("kubectl_", "flux_", "deploy_", "release_"), ToolPolicy("deploy", "high", True)),
(("image_",), ToolPolicy("artifact_write", "medium", False)),
(("video_", "vision_"), ToolPolicy("read_files", "low", False)),
(("artifact_",), ToolPolicy("artifact_write", "medium", False)),
(("memory_",), ToolPolicy("memory_write", "medium", False)),
(("delegate_", "subagent_"), ToolPolicy("delegate", "high", False)),
(("write_", "edit_", "file_"), ToolPolicy("write_files", "medium", False)),
)
UNKNOWN = ToolPolicy("external_side_effect", "high", True)
def classify(tool_name: str) -> ToolPolicy:
"""Return a known mapping, treating every unknown tool as high-risk external."""
name = tool_name.strip().lower() if isinstance(tool_name, str) else ""
if name in EXACT:
return EXACT[name]
for prefixes, policy in PREFIXES:
if name.startswith(prefixes):
return policy
return UNKNOWN