ouroboros/tests/test_python_interpreter.py
Andrei Kaznacheev 7d62c5afa3 feat(remote): three-phase dispatch — prepare once, project three ways, execute
The old shape was "look at the arguments, then run", so every guard that needed a
fact about the target fetched it itself, whenever it happened to need it. Three
failures follow at once: the same fact is computed repeatedly and may disagree
with itself; the guard is hard to reason about because its inputs arrive from
everywhere; and on a remote placement most of those fetches answer about the
wrong machine.

PREPARE reads the sealed placement ONCE and derives everything from that single
read — the fact door, the executor projection, and for an ssh placement the one
bundled prepare the target answers. A transitive AST test walks the pipeline and
requires every other placement read to be DECLARED with its reason; the earlier
scan read two function bodies and no further, so extracting the gate chain took
its reads out of view in the same commit that introduced them.

AUTHORIZE projects the final arguments into three NAMED views — handler, guard,
execution — bound immutably together, with every intentional difference declared
in a table. The guard projection used to be passed around as "the args", so the
authorized set and the executed set were two look-alike objects one rename apart.

EXECUTE hands the bound token to the target or continues on Home, and the
question that decides which is whether the operation ADDRESSES Home, not whether
the task is remote: by the ratified root matrix a remote task calling a
HOME-native root keeps its Home handler, and asking the placement instead sent
every guard keyed on the Home binding to its empty fallback.
2026-08-15 18:55:27 +04:00

494 lines
17 KiB
Python

from __future__ import annotations
import json
import pathlib
from types import SimpleNamespace
import pytest
from ouroboros.python_interpreter import resolve_process_python
from ouroboros.tools.registry import ToolContext, ToolRegistry
def _executable(path: pathlib.Path) -> pathlib.Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
path.chmod(0o755)
return path
def _venv_python(env_root: pathlib.Path) -> pathlib.Path:
"""Platform-correct fake venv interpreter (Scripts\\python.exe on Windows)."""
from ouroboros.platform_layer import IS_WINDOWS
if IS_WINDOWS:
return _executable(env_root / "Scripts" / "python.exe")
return _executable(env_root / "bin" / "python")
def _context(
tmp_path: pathlib.Path,
*,
workspace: pathlib.Path | None = None,
workspace_mode: str = "",
) -> ToolContext:
repo = tmp_path / "system_repo"
data = tmp_path / "data"
repo.mkdir(exist_ok=True)
data.mkdir(exist_ok=True)
if workspace is not None:
workspace.mkdir(parents=True, exist_ok=True)
return ToolContext(
repo_dir=repo,
system_repo_dir=repo,
drive_root=data,
workspace_root=workspace,
workspace_mode=workspace_mode,
task_id="python-resolver-test",
)
def _tool_args(tool_name: str, *, cwd: str = "", token: str = "python") -> dict:
if tool_name == "run_command":
return {"cmd": [token, "-V"], "cwd": cwd}
if tool_name == "run_script":
return {"script": "print('ok')", "interpreter": token, "cwd": cwd}
if tool_name == "start_service":
return {"name": "svc", "cmd": [token, "-V"], "cwd": cwd}
if tool_name == "verify_and_record":
return {
"contract_kind": "explicit_command",
"check": [token, "-V"],
"cwd": cwd,
}
raise AssertionError(tool_name)
def _interpreter(tool_name: str, args: dict) -> str:
if tool_name in {"run_command", "start_service"}:
return str(args["cmd"][0])
if tool_name == "run_script":
return str(args["interpreter"])
return str(args["check"][0])
@pytest.mark.parametrize(
"tool_name",
["run_command", "run_script", "start_service", "verify_and_record"],
)
def test_system_surfaces_use_validated_agent_python(tmp_path, monkeypatch, tool_name):
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
tool_name,
_tool_args(tool_name),
runtime_mode="advanced",
)
assert _interpreter(tool_name, resolved) == str(agent_python)
assert trace is not None
assert trace.surface == "system_repo"
assert trace.environment == "ouroboros_agent"
@pytest.mark.parametrize(
"tool_name",
["run_command", "run_script", "start_service", "verify_and_record"],
)
def test_external_workspace_prefers_project_venv(tmp_path, monkeypatch, tool_name):
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
project_python = _venv_python(workspace / ".venv")
(workspace / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
monkeypatch.setenv(
"OUROBOROS_AGENT_PYTHON",
str(_executable(tmp_path / "agent" / "bin" / "python")),
)
resolved, trace = resolve_process_python(
ctx,
tool_name,
_tool_args(tool_name),
runtime_mode="advanced",
)
assert _interpreter(tool_name, resolved) == str(project_python)
assert trace is not None
assert trace.surface == "external_workspace"
assert trace.environment == "project_venv"
def test_project_venv_platform_layouts(tmp_path, monkeypatch):
from ouroboros import platform_layer
env_root = tmp_path / ".venv"
env_root.mkdir()
(env_root / "pyvenv.cfg").write_text("home = test\n", encoding="utf-8")
posix_python = env_root / "bin" / "python"
posix_python.parent.mkdir()
posix_python.write_text("", encoding="utf-8")
posix_python.chmod(0o755)
monkeypatch.setattr(platform_layer, "IS_WINDOWS", False)
assert platform_layer.project_venv_python(tmp_path) == str(posix_python)
windows_python = env_root / "Scripts" / "python.exe"
windows_python.parent.mkdir()
windows_python.write_text("", encoding="utf-8")
windows_python.chmod(0o755)
monkeypatch.setattr(platform_layer, "IS_WINDOWS", True)
assert platform_layer.project_venv_python(tmp_path) == str(windows_python)
def test_user_files_project_venv_and_missing_venv_path_fallback(tmp_path, monkeypatch):
user_root = tmp_path / "user_files"
project = user_root / "project"
project.mkdir(parents=True)
monkeypatch.setenv("OUROBOROS_USER_FILES_ROOT", str(user_root))
ctx = _context(tmp_path)
project_python = _venv_python(project / ".venv")
(project / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd=str(project)),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == str(project_python)
assert trace is not None and trace.surface == "user_files"
(project / ".venv" / "pyvenv.cfg").unlink()
unresolved, fallback = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd=str(project)),
runtime_mode="advanced",
)
assert unresolved["cmd"][0] == "python"
assert fallback is not None
assert fallback.environment == "target_path"
assert fallback.fallback_reason == "project_venv_unavailable"
def test_executor_uses_backend_python_but_unmapped_task_drive_uses_agent(tmp_path, monkeypatch):
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
ctx.executor_ref = {
"type": "docker_exec",
"id": "programbench",
"container_name": "programbench",
"network": "none",
"workspace_host_path": str(workspace),
"workspace_backend_path": "/workspace",
}
_venv_python(workspace / ".venv")
(workspace / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command"),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == "python3"
assert trace is not None and trace.environment == "backend_path"
local, local_trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd="task_drive"),
runtime_mode="advanced",
)
assert local["cmd"][0] == str(agent_python)
assert local_trace is not None and local_trace.surface == "task_drive"
def test_reviewed_skill_environment_precedes_executor(tmp_path, monkeypatch):
import ouroboros.marketplace.isolated_deps as isolated_deps
import ouroboros.skill_loader as skill_loader
import ouroboros.skill_readiness as skill_readiness
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
ctx.task_metadata = {"source": "skill_scheduled_task", "skill": "demo"}
ctx.executor_ref = {
"type": "docker_exec",
"id": "executor",
"container_name": "executor",
"network": "none",
"workspace_host_path": str(workspace),
"workspace_backend_path": "/workspace",
}
skill_dir = tmp_path / "skill"
skill_dir.mkdir()
skill_python = _executable(skill_dir / ".ouroboros_env" / "python" / "bin" / "python")
loaded = SimpleNamespace(name="demo", skill_dir=skill_dir)
monkeypatch.setattr(skill_loader, "find_skill", lambda *args, **kwargs: loaded)
monkeypatch.setattr(
skill_readiness,
"skill_readiness_for_execution",
lambda *args, **kwargs: SimpleNamespace(ready=True),
)
monkeypatch.setattr(isolated_deps, "read_deps_state", lambda *args, **kwargs: {"status": "installed"})
monkeypatch.setattr(isolated_deps, "python_runtime_binary", lambda *args, **kwargs: skill_python)
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command"),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == str(skill_python)
assert trace is not None
assert trace.environment == "isolated_skill"
@pytest.mark.parametrize(
("tool_name", "args"),
[
("run_command", {"cmd": ["/usr/bin/python", "-V"]}),
("run_command", {"cmd": ["python3.12", "-V"]}),
("run_command", {"cmd": ["sh", "-c", "python -V"]}),
("run_command", {"cmd": ["env", "python", "-V"]}),
("run_script", {"script": "pass", "interpreter": "/usr/bin/python"}),
("run_script", {"script": "pass", "interpreter": "python3.12"}),
("verify_and_record", {"contract_kind": "explicit_command", "check": "python -V"}),
("verify_and_record", {"contract_kind": "artifact_observation", "check": ["python", "-V"]}),
("remote_exec", {"cmd": ["python", "-V"]}),
],
)
def test_noneligible_invocations_are_byte_for_byte_unchanged(tmp_path, tool_name, args):
ctx = _context(tmp_path)
resolved, trace = resolve_process_python(
ctx,
tool_name,
args,
runtime_mode="advanced",
)
assert resolved == args
assert trace is None
def test_light_run_script_default_cwd_uses_active_workspace_agent_python(tmp_path, monkeypatch):
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
"run_script",
{"script": "print('ok')"},
runtime_mode="light",
)
assert resolved["interpreter"] == str(agent_python)
assert trace is not None and trace.surface == "system_repo"
assert trace.target_root == "active_workspace"
def test_registry_guard_and_handler_receive_same_resolved_verify_argv(tmp_path, monkeypatch):
import ouroboros.safety as safety
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr(
safety,
"_run_llm_check",
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected safety LLM call")),
)
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
captured: dict[str, list[str]] = {}
# (RWS v2 §3.1) the guard projection is built by dispatch_args, not inline
# in the registry: patch the seam that actually produces guard_args.
from ouroboros.tools import dispatch_args as dispatch_args_module
original_guard = dispatch_args_module.process_shell_guard_args
def capture_guard(name, args, **kwargs):
guarded = original_guard(name, args, **kwargs)
captured["guard"] = list(guarded["cmd"])
return guarded
def capture_handler(_ctx, contract_kind, check, _resolved_binding=None, **kwargs):
assert contract_kind == "explicit_command"
assert _resolved_binding is not None
captured["handler"] = list(check)
return "ok"
monkeypatch.setattr(dispatch_args_module, "process_shell_guard_args", capture_guard)
monkeypatch.setattr(registry, "_run_shell_safety_check", lambda *args, **kwargs: "")
registry._entries["verify_and_record"].handler = capture_handler
result = registry.execute(
"verify_and_record",
{"contract_kind": "explicit_command", "check": ["python", "-m", "pytest", "--version"]},
)
expected = [str(agent_python), "-m", "pytest", "--version"]
assert result == "ok"
assert captured == {"guard": expected, "handler": expected}
events_path = ctx.drive_logs() / "events.jsonl"
event = json.loads(events_path.read_text(encoding="utf-8").splitlines()[-1])
assert event["type"] == "python_interpreter_resolution"
assert event["requested_interpreter"] == "python"
assert event["resolved_interpreter"] == str(agent_python)
assert "cmd" not in event
assert "check" not in event
def test_registry_uses_current_process_python_before_server_bootstrap(
tmp_path, monkeypatch,
):
import sys
ctx = _context(tmp_path)
monkeypatch.delenv("OUROBOROS_AGENT_PYTHON", raising=False)
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr("ouroboros.safety.check_safety", lambda *args, **kwargs: (True, ""))
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
observed = {}
def handler(_ctx, cmd, _resolved_binding=None, **_kwargs):
assert _resolved_binding is not None
observed["cmd"] = cmd
return "ok"
registry._entries["run_command"].handler = handler
result = registry.execute("run_command", {"cmd": ["python", "-V"]})
assert result == "ok"
assert observed["cmd"][0] == str(pathlib.Path(sys.executable).absolute())
def test_run_script_accepts_registry_attested_versioned_agent_python(
tmp_path, monkeypatch,
):
import ouroboros.tools.shell as shell
ctx = _context(tmp_path)
versioned_python = _executable(tmp_path / "agent" / "bin" / "python3.12")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(versioned_python))
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr("ouroboros.safety.check_safety", lambda *args, **kwargs: (True, ""))
monkeypatch.setattr(shell, "_run_shell", lambda *_args, **_kwargs: "ok")
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
monkeypatch.setattr(registry, "_run_shell_safety_check", lambda *args, **kwargs: "")
result = registry.execute(
"run_script",
{"script": "print('ok')", "interpreter": "python3"},
)
assert result.endswith("\nok")
assert "RUN_SCRIPT_BLOCKED" not in result
assert not hasattr(ctx, "_active_python_resolution")
def test_run_script_still_blocks_unattested_versioned_interpreter(tmp_path):
from ouroboros.tools.shell import _run_script
result = _run_script(
_context(tmp_path),
"print('unsafe')",
interpreter=str(tmp_path / "untrusted" / "python3.12"),
)
assert result.startswith("⚠️ RUN_SCRIPT_BLOCKED:")
def test_safety_fast_path_requires_matching_verified_resolver_provenance(tmp_path, monkeypatch):
import ouroboros.safety as safety
from ouroboros.python_interpreter import PythonResolutionTrace
agent_python = str(_executable(tmp_path / "agent" / "bin" / "python"))
verified = PythonResolutionTrace(
tool="run_command",
requested_interpreter="python",
resolved_interpreter=agent_python,
surface="system_repo",
environment="ouroboros_agent",
reason="agent_python",
)
calls: list[list[str]] = []
def llm_check(_tool, arguments, *_args, **_kwargs):
calls.append(list(arguments["cmd"]))
return False, "llm-called"
monkeypatch.setattr(safety, "_run_llm_check", llm_check)
safe = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-m", "pytest", "-q"]},
python_resolution=verified,
)
arbitrary = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-m", "pytest", "-q"]},
)
mismatch = safety.check_safety(
"run_command",
{"cmd": [str(tmp_path / "other" / "python"), "-m", "pytest", "-q"]},
python_resolution=verified,
)
assert safe == (True, "")
assert arbitrary == (False, "llm-called")
assert mismatch == (False, "llm-called")
assert len(calls) == 2
def test_verified_python_c_body_still_requires_safety_review(tmp_path, monkeypatch):
import ouroboros.safety as safety
from ouroboros.python_interpreter import PythonResolutionTrace
agent_python = str(_executable(tmp_path / "agent" / "bin" / "python"))
verified = PythonResolutionTrace(
tool="run_command",
requested_interpreter="python3",
resolved_interpreter=agent_python,
surface="system_repo",
environment="ouroboros_agent",
reason="agent_python",
)
monkeypatch.setattr(safety, "_run_llm_check", lambda *args, **kwargs: (False, "reviewed"))
result = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-c", "print('not allowlisted by module')"]},
python_resolution=verified,
)
assert result == (False, "reviewed")
def test_resolution_trace_failure_is_fail_soft(tmp_path, monkeypatch):
import ouroboros.python_interpreter as resolver
trace = resolver.PythonResolutionTrace(
tool="run_command",
requested_interpreter="python",
resolved_interpreter="python3",
surface="executor",
environment="backend_path",
reason="executor_backend_python3",
)
monkeypatch.setattr(resolver, "append_jsonl", lambda *args, **kwargs: (_ for _ in ()).throw(OSError("full")))
resolver.record_python_resolution(_context(tmp_path), trace)