Some checks failed
ci / validate (push) Has been cancelled
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
189 lines
7.5 KiB
Python
189 lines
7.5 KiB
Python
"""Execution transports derived from sand-boxer reachability descriptors.
|
|
|
|
The source checkout is an input to sandbox provisioning, never an execution
|
|
workspace. Once sand-boxer returns READY, every rein subprocess crosses exactly
|
|
one declared boundary: owner-mediated execution for a same-host namespace or
|
|
SSH for a remote workspace.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import uuid
|
|
from dataclasses import dataclass
|
|
from pathlib import Path, PurePosixPath
|
|
from typing import Any, Callable, Literal, Sequence
|
|
|
|
from glas_harness.contract import SandboxHandle
|
|
|
|
|
|
class TransportError(RuntimeError):
|
|
"""The sandbox reachability descriptor cannot safely execute commands."""
|
|
|
|
|
|
_SSH_TARGET = re.compile(
|
|
r"^(?:[A-Za-z0-9][A-Za-z0-9._-]*@)?[A-Za-z0-9][A-Za-z0-9._-]*$"
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ExecutionTransport:
|
|
kind: Literal["local_namespace", "ssh"]
|
|
workspace: str
|
|
pid: int | None = None
|
|
ssh_target: str | None = None
|
|
owner_execute: Callable | None = None
|
|
timeout_seconds: int = 600
|
|
|
|
def command(self, argv: Sequence[str]) -> list[str]:
|
|
if not argv:
|
|
raise TransportError("cannot execute an empty command")
|
|
command = [str(item) for item in argv]
|
|
scoped_command = [
|
|
"sh",
|
|
"-c",
|
|
'cd "$1" && shift && exec "$@"',
|
|
"sh",
|
|
self.workspace,
|
|
*command,
|
|
]
|
|
if self.kind == "local_namespace":
|
|
raise TransportError("local commands require sandbox owner execution")
|
|
if self.kind == "ssh":
|
|
if not self.ssh_target or not _SSH_TARGET.fullmatch(self.ssh_target):
|
|
raise TransportError(
|
|
"SSH target must be a single non-option host or user@host target"
|
|
)
|
|
return ["ssh", "--", self.ssh_target, shlex.join(scoped_command)]
|
|
raise TransportError(f"unsupported execution transport: {self.kind}")
|
|
|
|
def run(
|
|
self,
|
|
argv: Sequence[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
timeout: float | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
if not argv:
|
|
raise TransportError("cannot execute an empty command")
|
|
bounded_timeout = min(
|
|
timeout if timeout is not None else self.timeout_seconds,
|
|
self.timeout_seconds,
|
|
)
|
|
if self.kind == "local_namespace":
|
|
if self.owner_execute is None:
|
|
raise TransportError("local sandbox requires an owner execution binding")
|
|
if bounded_timeout < 1:
|
|
raise TransportError("owner execution timeout must be at least one second")
|
|
result = self.owner_execute(list(argv), input_text, int(bounded_timeout))
|
|
if result.timed_out:
|
|
raise subprocess.TimeoutExpired(list(argv), bounded_timeout)
|
|
if result.output_truncated:
|
|
raise TransportError("sandbox owner execution output was truncated")
|
|
return subprocess.CompletedProcess(
|
|
list(argv), result.exit_code, result.stdout, result.stderr
|
|
)
|
|
return subprocess.run(
|
|
self.command(argv),
|
|
input=input_text,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=bounded_timeout,
|
|
)
|
|
|
|
def resolve_executable(self, name: str, *, timeout: float = 15.0) -> str:
|
|
if not name or "/" in name:
|
|
raise TransportError("rein executable must be a command name, not a host path")
|
|
proc = self.run(
|
|
["sh", "-c", 'command -v -- "$1"', "sh", name], timeout=timeout
|
|
)
|
|
resolved = proc.stdout.strip()
|
|
if proc.returncode != 0 or not resolved:
|
|
raise TransportError(
|
|
f"'{name}' is not installed inside the selected sandbox transport"
|
|
)
|
|
return resolved
|
|
|
|
def git_head(self, *, timeout: float = 15.0) -> str:
|
|
proc = self.run(
|
|
["git", "-C", self.workspace, "rev-parse", "HEAD"], timeout=timeout
|
|
)
|
|
head = proc.stdout.strip()
|
|
if proc.returncode != 0 or not head:
|
|
raise TransportError(
|
|
"cannot read git HEAD inside the selected sandbox transport"
|
|
)
|
|
return head
|
|
|
|
def write_task_file(self, payload: dict[str, Any]) -> str:
|
|
task_name = f".glas-harness-task-{uuid.uuid4().hex}.json"
|
|
# Keep the task spec under Git's private metadata directory so an
|
|
# agent's broad `git add -A` cannot accidentally commit its prompt.
|
|
task_path = str(PurePosixPath(self.workspace) / ".git" / task_name)
|
|
content = json.dumps(payload)
|
|
script = 'umask 077; set -C; cat > "$1"'
|
|
proc = self.run(["sh", "-c", script, "sh", task_path], input_text=content, timeout=15)
|
|
if proc.returncode != 0:
|
|
raise TransportError(
|
|
"cannot create sandbox task file: "
|
|
+ (proc.stderr.strip() or f"exit {proc.returncode}")
|
|
)
|
|
return task_path
|
|
|
|
def remove_file(self, path: str) -> None:
|
|
proc = self.run(["rm", "-f", "--", path], timeout=15)
|
|
if proc.returncode != 0:
|
|
raise TransportError(
|
|
"cannot remove sandbox task file: "
|
|
+ (proc.stderr.strip() or f"exit {proc.returncode}")
|
|
)
|
|
|
|
|
|
def transport_from_sandbox(sandbox: SandboxHandle) -> ExecutionTransport:
|
|
reachability = sandbox.reachability
|
|
pid = reachability.get("pid")
|
|
workspace = reachability.get("workspace_dir")
|
|
ssh_target = reachability.get("ssh")
|
|
remote_dir = reachability.get("remote_dir")
|
|
|
|
has_local = pid is not None or workspace is not None
|
|
has_remote = ssh_target is not None or remote_dir is not None
|
|
if has_local and has_remote:
|
|
raise TransportError("ambiguous sandbox reachability: local and SSH fields coexist")
|
|
if has_local:
|
|
if not pid or not workspace:
|
|
raise TransportError(
|
|
"incomplete local sandbox reachability: pid and workspace_dir are required"
|
|
)
|
|
try:
|
|
parsed_pid = int(str(pid))
|
|
except ValueError as exc:
|
|
raise TransportError("local sandbox reachability pid must be an integer") from exc
|
|
if parsed_pid <= 0 or not Path(str(workspace)).is_absolute():
|
|
raise TransportError("local sandbox reachability has invalid pid or workspace_dir")
|
|
if sandbox._owner_execute is None:
|
|
raise TransportError("local sandbox requires an owner execution binding")
|
|
return ExecutionTransport(
|
|
kind="local_namespace", workspace=str(workspace), pid=parsed_pid,
|
|
owner_execute=sandbox._owner_execute,
|
|
timeout_seconds=sandbox._timeout_seconds,
|
|
)
|
|
if has_remote:
|
|
if not ssh_target or not remote_dir:
|
|
raise TransportError(
|
|
"incomplete remote sandbox reachability: ssh and remote_dir are required"
|
|
)
|
|
if not _SSH_TARGET.fullmatch(str(ssh_target)):
|
|
raise TransportError(
|
|
"SSH reachability must be a single non-option host or user@host target"
|
|
)
|
|
if not PurePosixPath(str(remote_dir)).is_absolute():
|
|
raise TransportError("remote sandbox workspace must be an absolute path")
|
|
return ExecutionTransport(
|
|
kind="ssh", workspace=str(remote_dir), ssh_target=str(ssh_target),
|
|
timeout_seconds=sandbox._timeout_seconds
|
|
)
|
|
raise TransportError("sandbox exposes no supported execution reachability")
|