Some checks failed
Governed runtime contract / contract (push) Failing after 10s
metered-once --rehearse runs the admitted runtime's launchers (and claude --version when the build declares it) inside bwrap: runtime read-only at the mount path, no network, cleared environment, no credentials, no claim. Fails on the deleted-build-interpreter trampoline that failed run 6efa9436; the corrected runtime ee3d0a53 passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 269692@bnt-lap001 Assistant-Session: 8077e68a-0985-4b41-898c-2dc602b0d6be
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
"""Free host rehearsal of an admitted owner runtime (REINAH-WP-0008-T01).
|
|
|
|
The owner check reads only the first line of each launcher. The 2026-09-27
|
|
run 6efa9436 passed that and still failed with exit 127, because a launcher
|
|
re-executed a deleted build-host interpreter. This rehearsal runs the
|
|
launchers the way the sandbox does: inside bwrap, with the runtime read-only
|
|
at the mount path, no network, a cleared environment and nothing else
|
|
writable. It uses no credentials, provider route, queue or ledger.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Callable, Sequence
|
|
|
|
RUNTIME_MOUNT = "/opt/sandboxer/runtime"
|
|
# Same read-only system binds as sand-boxer's bwrap extension.
|
|
SYSTEM_RO_BINDS = ("/usr", "/bin", "/lib", "/lib64", "/etc/resolv.conf", "/etc/ssl/certs")
|
|
PYTHON_LAUNCHERS = ("rein-aharness", "glas-harness")
|
|
TIMEOUT_SECONDS = 60
|
|
|
|
Runner = Callable[[Sequence[str], int], int]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Step:
|
|
name: str
|
|
exit_code: int
|
|
|
|
@property
|
|
def ok(self) -> bool:
|
|
return self.exit_code == 0
|
|
|
|
|
|
def bwrap_argv(runtime: Path, command: Sequence[str], *, bwrap_bin: str = "bwrap") -> list[str]:
|
|
argv = [
|
|
bwrap_bin,
|
|
"--unshare-user", "--unshare-pid", "--unshare-ipc",
|
|
"--unshare-uts", "--unshare-cgroup", "--unshare-net",
|
|
"--tmpfs", "/", "--proc", "/proc", "--dev", "/dev", "--clearenv",
|
|
]
|
|
for path in SYSTEM_RO_BINDS:
|
|
if Path(path).exists():
|
|
argv += ["--ro-bind", path, path]
|
|
argv += [
|
|
"--dir", "/opt", "--dir", "/opt/sandboxer",
|
|
"--ro-bind", str(runtime), RUNTIME_MOUNT,
|
|
"--dir", "/tmp",
|
|
"--setenv", "HOME", "/tmp",
|
|
"--setenv", "PATH", f"{RUNTIME_MOUNT}/bin:/usr/bin:/bin",
|
|
"--chdir", "/tmp",
|
|
]
|
|
return argv + list(command)
|
|
|
|
|
|
def _run(argv: Sequence[str], timeout: int) -> int:
|
|
try:
|
|
return subprocess.run(
|
|
list(argv), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL, timeout=timeout,
|
|
).returncode
|
|
except subprocess.TimeoutExpired:
|
|
return 124
|
|
except OSError:
|
|
return 127
|
|
|
|
|
|
def _expects_claude(runtime: Path) -> bool:
|
|
try:
|
|
info = json.loads((runtime / "build-info.json").read_text())
|
|
except (OSError, ValueError):
|
|
return False
|
|
return isinstance(info, dict) and isinstance(info.get("claude"), dict)
|
|
|
|
|
|
def rehearse(runtime: Path, *, run: Runner = _run, bwrap_bin: str | None = None) -> list[Step]:
|
|
"""Run each launcher once inside bwrap; return one step per launcher."""
|
|
bwrap = bwrap_bin or shutil.which("bwrap")
|
|
if bwrap is None:
|
|
return [Step("bwrap", 127)]
|
|
commands = [(name, [f"{RUNTIME_MOUNT}/bin/{name}", "--help"]) for name in PYTHON_LAUNCHERS]
|
|
if _expects_claude(runtime):
|
|
commands.append(("claude", [f"{RUNTIME_MOUNT}/bin/claude", "--version"]))
|
|
return [
|
|
Step(name, run(bwrap_argv(runtime, command, bwrap_bin=bwrap), TIMEOUT_SECONDS))
|
|
for name, command in commands
|
|
]
|