Run every command on the build VM over SSH (M1.3)
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 55s
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 55s
execution.SSHHost.execute is now the only way the agent runs anything (L13). Commands travel as a script on SSH's stdin, so the secrets handed to a call are never on a command line, and every value handed out is redacted from the output. Timeouts kill the remote process group, and an unreachable host is reported as such, not as a failed command. Workspace and GitRepo run on the build VM. The clone uses the repository's SSH URL, so no credential is stored in it (#89); fetches and pushes get the push key over stdin, in a short-lived ssh-agent, and PRs are still opened from the agent's host with its API token. The bench runs on the build host too. Tests reach "the build VM" through a throwaway local sshd (nisse_dev .testing); CI installs openssh-server. The live push test now pushes with the sandbox's deploy key (NISSE_PUSH_KEY). Fixes #89. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
b7f1f3371c
commit
761ebdb857
32 files changed
+1098
-280
No files matched your search
@@ -3,6 +3,7 @@ delegation harness and the comment poller — were fully built, fully tested,
|
||||
and reachable from nothing but their own tests. Unit tests that construct a
|
||||
component directly cannot catch that; only assembly can."""
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -13,7 +14,7 @@ from test_config import SECRETS, tree
|
||||
from nisse_dev.agent import build_agent, discover_models, prepare_workspace
|
||||
from nisse_dev.catalog import ModelCatalog, UnknownModel
|
||||
from nisse_dev.config import AgentConfig
|
||||
from nisse_dev.git import GitRepo
|
||||
from nisse_dev.execution import SSHHost
|
||||
from nisse_dev.oplog import OpLog
|
||||
|
||||
# What the test roles resolve to, as LiteLLM would report them.
|
||||
@@ -36,47 +37,77 @@ TEST_CATALOG = ModelCatalog.from_payload(
|
||||
)
|
||||
|
||||
|
||||
# The build VM for every test here: the session's throwaway sshd.
|
||||
HOST: SSHHost
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _build_host(ssh_host: SSHHost) -> None:
|
||||
global HOST
|
||||
HOST = ssh_host
|
||||
|
||||
|
||||
def config_for(tmp_path: Path, **changes: str) -> AgentConfig:
|
||||
return AgentConfig.from_tree(
|
||||
tree(
|
||||
state_db=str(tmp_path / "state.db"),
|
||||
workspace=str(tmp_path / "work"),
|
||||
build_host__address=HOST.address,
|
||||
build_host__user=HOST.user,
|
||||
build_host__port=str(HOST.port),
|
||||
build_host__key=str(HOST._key),
|
||||
**changes,
|
||||
),
|
||||
SECRETS,
|
||||
)
|
||||
|
||||
|
||||
def git(*args: str) -> None:
|
||||
subprocess.run(["git", *args], check=True, capture_output=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def origin(tmp_path: Path) -> Path:
|
||||
"""A bare repo standing in for the project on the forge."""
|
||||
remote = tmp_path / "origin.git"
|
||||
GitRepo(tmp_path)._run("init", "-q", "--bare", "-b", "main", str(remote))
|
||||
seed = GitRepo(tmp_path / "seed")
|
||||
seed.init()
|
||||
seed.configure_identity("seed", "seed@test")
|
||||
(seed.root / "README.md").write_text("project\n")
|
||||
seed.commit_all("seed")
|
||||
seed.push(str(remote), "main")
|
||||
git("init", "-q", "--bare", "-b", "main", str(remote))
|
||||
seed = tmp_path / "seed"
|
||||
git("init", "-q", "-b", "main", str(seed))
|
||||
(seed / "README.md").write_text("project\n")
|
||||
git("-C", str(seed), "add", "-A")
|
||||
git(
|
||||
"-C",
|
||||
str(seed),
|
||||
"-c",
|
||||
"user.name=s",
|
||||
"-c",
|
||||
"user.email=s@t",
|
||||
"commit",
|
||||
"-qm",
|
||||
"s",
|
||||
)
|
||||
git("-C", str(seed), "push", "-q", str(remote), "HEAD:refs/heads/main")
|
||||
return remote
|
||||
|
||||
|
||||
def test_workspace_is_cloned_when_absent(tmp_path: Path, origin: Path) -> None:
|
||||
config = config_for(tmp_path, gitea__url=f"file://{origin}")
|
||||
# authenticated_url would mangle a file:// remote, so clone directly.
|
||||
GitRepo(tmp_path).clone(str(origin), config.workspace)
|
||||
repo = prepare_workspace(config, "nisse-dev-testing")
|
||||
def test_workspace_is_cloned_on_the_build_host_when_absent(
|
||||
tmp_path: Path, origin: Path
|
||||
) -> None:
|
||||
config = config_for(tmp_path)
|
||||
repo = prepare_workspace(HOST, config, "nisse-dev-testing", str(origin), None)
|
||||
|
||||
assert (config.workspace / "README.md").exists()
|
||||
assert repo.current_branch() == "main"
|
||||
# The clone's configuration holds the plain URL it was given (#89).
|
||||
assert str(origin) in (config.workspace / ".git" / "config").read_text()
|
||||
|
||||
|
||||
def test_workspace_is_reused_when_already_cloned(tmp_path: Path, origin: Path) -> None:
|
||||
config = config_for(tmp_path)
|
||||
GitRepo(tmp_path).clone(str(origin), config.workspace)
|
||||
prepare_workspace(HOST, config, "nisse-dev-testing", str(origin), None)
|
||||
(config.workspace / "local-work.txt").write_text("in progress")
|
||||
|
||||
prepare_workspace(config, "nisse-dev-testing")
|
||||
prepare_workspace(HOST, config, "nisse-dev-testing", str(origin), None)
|
||||
|
||||
# Re-cloning would silently destroy uncommitted work.
|
||||
assert (config.workspace / "local-work.txt").exists()
|
||||
@@ -110,7 +141,6 @@ class FakeTransport:
|
||||
def build_offline(tmp_path: Path, origin: Path, monkeypatch: Any) -> Any:
|
||||
"""Assemble the real graph with only the network boundaries faked."""
|
||||
config = config_for(tmp_path)
|
||||
GitRepo(tmp_path).clone(str(origin), config.workspace)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nisse_dev.agent.GiteaClient.whoami", lambda self: "nisse-dev-testing"
|
||||
@@ -121,8 +151,7 @@ def build_offline(tmp_path: Path, origin: Path, monkeypatch: Any) -> Any:
|
||||
)
|
||||
monkeypatch.setattr("nisse_dev.agent.MatrixTransport", lambda cfg: FakeTransport())
|
||||
monkeypatch.setattr(
|
||||
"nisse_dev.agent.authenticated_url",
|
||||
lambda base, repo, user, token: str(origin),
|
||||
"nisse_dev.agent.GiteaClient.ssh_url", lambda self, repo: str(origin)
|
||||
)
|
||||
return build_agent(config)
|
||||
|
||||
@@ -317,7 +346,6 @@ def test_tuning_reaches_every_component(
|
||||
tools=ToolsTuning(command_timeout_s=300, max_output_chars=2000),
|
||||
),
|
||||
)
|
||||
GitRepo(tmp_path).clone(str(origin), config.workspace)
|
||||
monkeypatch.setattr(
|
||||
"nisse_dev.agent.GiteaClient.whoami", lambda self: "nisse-dev-testing"
|
||||
)
|
||||
@@ -327,8 +355,7 @@ def test_tuning_reaches_every_component(
|
||||
)
|
||||
monkeypatch.setattr("nisse_dev.agent.MatrixTransport", lambda cfg: FakeTransport())
|
||||
monkeypatch.setattr(
|
||||
"nisse_dev.agent.authenticated_url",
|
||||
lambda base, repo, user, token: str(origin),
|
||||
"nisse_dev.agent.GiteaClient.ssh_url", lambda self, repo: str(origin)
|
||||
)
|
||||
agent = build_agent(config)
|
||||
executor = agent.orchestrator._executor
|
||||
|
||||
Reference in new issue
Block a user