197 lines
6.4 KiB
Python
197 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
from opensquilla.provider.types import ToolDefinition, ToolInputSchema
|
|
from opensquilla.tools.policy import ToolSurfaceCapabilities
|
|
from opensquilla.tools.types import (
|
|
CallerKind,
|
|
InteractionMode,
|
|
RegisteredTool,
|
|
ToolContext,
|
|
ToolSpec,
|
|
)
|
|
from opensquilla.tools.visibility import (
|
|
ToolProfile,
|
|
effective_tool_context,
|
|
filter_by_profile,
|
|
profile_allows_tool,
|
|
resolve_profile,
|
|
visible_registered_tools,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
REGISTRY = ROOT / "src/opensquilla/tools/registry.py"
|
|
VISIBILITY = ROOT / "src/opensquilla/tools/visibility.py"
|
|
|
|
|
|
async def _handler() -> str:
|
|
return "ok"
|
|
|
|
|
|
def _registered_tool(
|
|
name: str,
|
|
*,
|
|
exposed_by_default: bool = True,
|
|
owner_only: bool = False,
|
|
) -> RegisteredTool:
|
|
return RegisteredTool(
|
|
spec=ToolSpec(
|
|
name=name,
|
|
description=f"{name} tool",
|
|
parameters={},
|
|
exposed_by_default=exposed_by_default,
|
|
owner_only=owner_only,
|
|
),
|
|
handler=_handler,
|
|
)
|
|
|
|
|
|
def _tool_definition(name: str) -> ToolDefinition:
|
|
return ToolDefinition(
|
|
name=name,
|
|
description=f"{name} tool",
|
|
input_schema=ToolInputSchema(type="object", properties={}, required=[]),
|
|
)
|
|
|
|
|
|
def _imports_from(path: Path) -> set[tuple[str, str]]:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
imports: set[tuple[str, str]] = set()
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.ImportFrom) and node.module:
|
|
for alias in node.names:
|
|
imports.add((node.module, alias.name))
|
|
return imports
|
|
|
|
|
|
def _top_level_classes(path: Path) -> set[str]:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
return {node.name for node in tree.body if isinstance(node, ast.ClassDef)}
|
|
|
|
|
|
def _top_level_functions(path: Path) -> set[str]:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
return {
|
|
node.name
|
|
for node in tree.body
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
}
|
|
|
|
|
|
def _top_level_assignments(path: Path) -> set[str]:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
names: set[str] = set()
|
|
for node in tree.body:
|
|
if isinstance(node, ast.Assign):
|
|
for target in node.targets:
|
|
if isinstance(target, ast.Name):
|
|
names.add(target.id)
|
|
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
names.add(node.target.id)
|
|
return names
|
|
|
|
|
|
def test_registry_delegates_visibility_policy_to_tools_visibility_boundary() -> None:
|
|
imports = _imports_from(REGISTRY)
|
|
|
|
assert ("opensquilla.tools", "visibility") in imports
|
|
|
|
registry_classes = _top_level_classes(REGISTRY)
|
|
registry_functions = _top_level_functions(REGISTRY)
|
|
registry_assignments = _top_level_assignments(REGISTRY)
|
|
assert "ToolProfile" not in registry_classes
|
|
assert "filter_by_profile" not in registry_functions
|
|
assert "profile_allows_tool" not in registry_functions
|
|
assert "resolve_profile" not in registry_functions
|
|
assert {
|
|
"ToolProfile",
|
|
"_CHANNEL_DEFAULT_ALLOW",
|
|
"_CHANNEL_HARD_DENY_NON_OWNER",
|
|
"filter_by_profile",
|
|
"profile_allows_tool",
|
|
"resolve_profile",
|
|
} <= registry_assignments
|
|
|
|
visibility_classes = _top_level_classes(VISIBILITY)
|
|
visibility_functions = _top_level_functions(VISIBILITY)
|
|
visibility_assignments = _top_level_assignments(VISIBILITY)
|
|
assert "ToolProfile" in visibility_classes
|
|
assert "filter_by_profile" in visibility_functions
|
|
assert "profile_allows_tool" in visibility_functions
|
|
assert "resolve_profile" in visibility_functions
|
|
assert "effective_tool_context" in visibility_functions
|
|
assert "visible_registered_tools" in visibility_functions
|
|
assert "_CHANNEL_DEFAULT_ALLOW" in visibility_assignments
|
|
|
|
|
|
def test_visibility_boundary_preserves_channel_profile_filtering() -> None:
|
|
channel_ctx = ToolContext(is_owner=False, caller_kind=CallerKind.CHANNEL)
|
|
profile = resolve_profile(channel_ctx)
|
|
|
|
filtered = filter_by_profile(
|
|
[
|
|
_tool_definition("publish_artifact"),
|
|
_tool_definition("git_commit"),
|
|
_tool_definition("read_file"),
|
|
],
|
|
profile,
|
|
)
|
|
|
|
assert profile is ToolProfile.CHANNEL_DEFAULT
|
|
assert [tool.name for tool in filtered] == ["publish_artifact", "read_file"]
|
|
assert profile_allows_tool("cron", profile) is True
|
|
assert profile_allows_tool("git_commit", profile) is False
|
|
|
|
|
|
def test_visibility_boundary_preserves_context_visibility_rules() -> None:
|
|
tools = [
|
|
_registered_tool("visible"),
|
|
_registered_tool("owner_only", owner_only=True),
|
|
_registered_tool("hidden", exposed_by_default=False),
|
|
]
|
|
ctx = ToolContext(
|
|
is_owner=False,
|
|
caller_kind=CallerKind.CHANNEL,
|
|
allowed_tools={"visible", "hidden", "owner_only"},
|
|
surfaced_tools={"hidden"},
|
|
)
|
|
|
|
visible = visible_registered_tools(tools, ctx, sort=True)
|
|
|
|
assert [tool.spec.name for tool in visible] == ["hidden", "visible"]
|
|
|
|
|
|
def test_visibility_boundary_preserves_effective_runtime_contexts() -> None:
|
|
subagent_ctx = effective_tool_context(
|
|
session_key="subagent:worker",
|
|
caller_kind=None,
|
|
interaction_mode=None,
|
|
tool_surface_capabilities=ToolSurfaceCapabilities(session_manager=True),
|
|
is_owner=True,
|
|
)
|
|
owner_cron_ctx = effective_tool_context(
|
|
session_key="cron:nightly",
|
|
caller_kind=None,
|
|
interaction_mode=None,
|
|
tool_surface_capabilities=ToolSurfaceCapabilities(scheduler=True),
|
|
is_owner=True,
|
|
)
|
|
channel_ctx = effective_tool_context(
|
|
caller_kind=CallerKind.CHANNEL,
|
|
tool_surface_capabilities=ToolSurfaceCapabilities(
|
|
channel_backing=True,
|
|
scheduler=True,
|
|
),
|
|
is_owner=False,
|
|
)
|
|
|
|
assert subagent_ctx.caller_kind is CallerKind.SUBAGENT
|
|
assert subagent_ctx.interaction_mode is InteractionMode.UNATTENDED
|
|
assert "publish_artifact" in subagent_ctx.denied_tools
|
|
assert owner_cron_ctx.caller_kind is CallerKind.CRON
|
|
assert owner_cron_ctx.is_owner is True
|
|
assert channel_ctx.caller_kind is CallerKind.CHANNEL
|
|
assert "cron" in (channel_ctx.allowed_tools or set())
|