209 lines
7.8 KiB
Python
209 lines
7.8 KiB
Python
|
|
"""SCM assignment tests that keep coordinator credentials out of worktrees."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
from http.server import BaseHTTPRequestHandler
|
||
|
|
from types import SimpleNamespace
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
import yaml
|
||
|
|
|
||
|
|
|
||
|
|
SCRIPTS = Path(__file__).parents[2] / "services/hermes/scripts"
|
||
|
|
HERMES = SCRIPTS.parent
|
||
|
|
sys.path[:0] = [str(SCRIPTS), str(HERMES / "scm-common/scripts")]
|
||
|
|
|
||
|
|
import execution_pool_coordinator as coordinator # noqa: E402
|
||
|
|
import execution_pool_project as project # noqa: E402
|
||
|
|
import execution_pool_protocol as protocol # noqa: E402
|
||
|
|
import execution_pool_scm as scm # noqa: E402
|
||
|
|
import execution_pool_worker as worker # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
KEY = b"k" * 32
|
||
|
|
|
||
|
|
|
||
|
|
def test_new_task_uses_explicit_reviewed_atlas_default(monkeypatch):
|
||
|
|
monkeypatch.setattr(
|
||
|
|
project,
|
||
|
|
"resolve_project",
|
||
|
|
lambda board: (
|
||
|
|
f"https://scm.bstein.dev/atlas/{board}.git", "main", Path("/unused")
|
||
|
|
),
|
||
|
|
)
|
||
|
|
task = SimpleNamespace(id="t_deadbeef", workspace_path="", branch_name="")
|
||
|
|
|
||
|
|
assert project.resolve_assignment("titan-iac", task) == (
|
||
|
|
"https://scm.bstein.dev/atlas/titan-iac.git",
|
||
|
|
"wt/t_deadbeef",
|
||
|
|
"main",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_legacy_local_workspace_remains_owned_by_local_lane(tmp_path):
|
||
|
|
task = SimpleNamespace(
|
||
|
|
id="t_deadbeef", workspace_path=str(tmp_path), branch_name="feature/safe"
|
||
|
|
)
|
||
|
|
|
||
|
|
assert project.distributed_workspace_eligible(task) is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_local_git_environment_excludes_credential_boundary_paths():
|
||
|
|
local = scm._git_environment()
|
||
|
|
assert "HERMES_SCM_PASSWORD_FILE" not in local
|
||
|
|
assert "GIT_ASKPASS" not in local
|
||
|
|
assert "GITEA_TOKEN" not in local
|
||
|
|
|
||
|
|
|
||
|
|
def test_finalized_duplicate_result_is_acknowledged_without_refinalizing(tmp_path):
|
||
|
|
binding = {
|
||
|
|
"board": "atlas", "task_id": "t_deadbeef", "run_id": "run-1",
|
||
|
|
"worker_ordinal": 0, "attempt": 1,
|
||
|
|
}
|
||
|
|
store = protocol.PoolStore(tmp_path / "pool.db")
|
||
|
|
store.add(binding, {"context": "safe"})
|
||
|
|
ordinal_key = protocol.derive_ordinal_key(KEY, 0)
|
||
|
|
result = protocol.sign_envelope(ordinal_key, "result", binding, {"structured": {}})
|
||
|
|
store.accept_result(result)
|
||
|
|
store.finalize(binding, "finalized")
|
||
|
|
pool = coordinator.Coordinator(KEY, store)
|
||
|
|
called = []
|
||
|
|
pool.finalize = called.append
|
||
|
|
|
||
|
|
assert pool.result(result)["payload"]["duplicate"] is True
|
||
|
|
assert called == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_provider_sessions_are_bound_to_exact_task_run(tmp_path, monkeypatch):
|
||
|
|
runtime = tmp_path / "runtime"
|
||
|
|
(runtime / "codex").mkdir(parents=True)
|
||
|
|
(runtime / "claude").mkdir(parents=True)
|
||
|
|
monkeypatch.setattr(worker, "ROOT", tmp_path / "worker")
|
||
|
|
(tmp_path / "worker/provider-state").mkdir(parents=True)
|
||
|
|
monkeypatch.setattr(worker.cli_lane_runner, "DATA_ROOT", tmp_path / "worker-data")
|
||
|
|
(tmp_path / "worker-data").mkdir()
|
||
|
|
monkeypatch.setenv("CODEX_HOME", str(runtime / "codex"))
|
||
|
|
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(runtime / "claude"))
|
||
|
|
assignment = {"board": "atlas", "task_id": "t_deadbeef", "run_id": "run-1"}
|
||
|
|
|
||
|
|
worker._bind_provider_sessions(assignment)
|
||
|
|
|
||
|
|
for path in (
|
||
|
|
runtime / "codex/sessions", runtime / "claude/projects",
|
||
|
|
runtime / "claude/session-env", runtime / "claude/todos",
|
||
|
|
):
|
||
|
|
assert path.is_symlink()
|
||
|
|
assert "atlas/t_deadbeef/run-1" in str(path.resolve())
|
||
|
|
assert (tmp_path / "worker-data/home").is_symlink()
|
||
|
|
assert "atlas/t_deadbeef/run-1" in str((tmp_path / "worker-data/home").resolve())
|
||
|
|
|
||
|
|
|
||
|
|
def test_internal_http_server_rejects_work_above_its_bound(monkeypatch):
|
||
|
|
server = protocol.BoundedHTTPServer(
|
||
|
|
("127.0.0.1", 0), BaseHTTPRequestHandler, max_workers=1
|
||
|
|
)
|
||
|
|
rejected = []
|
||
|
|
monkeypatch.setattr(server, "shutdown_request", rejected.append)
|
||
|
|
assert server._slots.acquire(blocking=False)
|
||
|
|
try:
|
||
|
|
marker = object()
|
||
|
|
server.process_request(marker, ("127.0.0.1", 1))
|
||
|
|
assert rejected == [marker]
|
||
|
|
finally:
|
||
|
|
server._slots.release()
|
||
|
|
server.server_close()
|
||
|
|
|
||
|
|
|
||
|
|
def test_additive_patch_replaces_local_lane_without_touching_base_deployment():
|
||
|
|
patch = yaml.safe_load((HERMES / "execution-coordinator-patch.yaml").read_text())
|
||
|
|
containers = patch["spec"]["template"]["spec"]["containers"]
|
||
|
|
local = next(item for item in containers if item["name"] == "cli-lane-runner")
|
||
|
|
pool = next(item for item in containers if item["name"] == "execution-pool-coordinator")
|
||
|
|
environment = {item["name"]: item["value"] for item in local["env"]}
|
||
|
|
assert environment == {
|
||
|
|
"HERMES_CLI_LANE_OWNED_WORKSPACES_ONLY": "true",
|
||
|
|
"HERMES_CLI_LANE_CONCURRENCY": "1",
|
||
|
|
}
|
||
|
|
assert pool["resources"]["requests"] == {"cpu": "50m", "memory": "128Mi"}
|
||
|
|
access = next(item for item in pool["volumeMounts"] if item["name"] == "runtime-access")
|
||
|
|
assert access["subPath"] == "execution-pool-key" and access["readOnly"] is True
|
||
|
|
|
||
|
|
|
||
|
|
def test_additive_network_policies_expose_only_worker_pool_and_switchyard_ports():
|
||
|
|
documents = list(yaml.safe_load_all(
|
||
|
|
(HERMES / "execution-worker-networkpolicy.yaml").read_text()
|
||
|
|
))
|
||
|
|
policies = {item["metadata"]["name"]: item for item in documents}
|
||
|
|
assert set(policies) == {
|
||
|
|
"hermes-execution-worker-isolation",
|
||
|
|
"hermes-execution-mediator-isolation",
|
||
|
|
"hermes-execution-pool-ingress",
|
||
|
|
"hermes-execution-switchyard-ingress",
|
||
|
|
*(f"hermes-execution-worker-mediator-{ordinal}" for ordinal in range(3)),
|
||
|
|
*(f"hermes-execution-mediator-worker-{ordinal}" for ordinal in range(3)),
|
||
|
|
}
|
||
|
|
assert policies["hermes-execution-pool-ingress"]["spec"]["ingress"][0]["ports"] == [
|
||
|
|
{"protocol": "TCP", "port": 9007}
|
||
|
|
]
|
||
|
|
pool_source = policies["hermes-execution-pool-ingress"]["spec"]["ingress"]
|
||
|
|
assert "hermes-execution-mediator" in str(pool_source)
|
||
|
|
worker_egress = policies["hermes-execution-worker-isolation"]["spec"]["egress"]
|
||
|
|
assert "hermes-scm-broker" not in str(worker_egress)
|
||
|
|
assert "hermes-execution-mediator" not in str(worker_egress)
|
||
|
|
for ordinal in range(3):
|
||
|
|
worker_policy = policies[f"hermes-execution-worker-mediator-{ordinal}"]
|
||
|
|
mediator_policy = policies[f"hermes-execution-mediator-worker-{ordinal}"]
|
||
|
|
assert worker_policy["spec"]["podSelector"]["matchLabels"][
|
||
|
|
"apps.kubernetes.io/pod-index"
|
||
|
|
] == str(ordinal)
|
||
|
|
assert worker_policy["spec"]["egress"][0]["to"][0]["podSelector"][
|
||
|
|
"matchLabels"
|
||
|
|
]["pool-ordinal"] == str(ordinal)
|
||
|
|
assert mediator_policy["spec"]["ingress"][0]["from"][0]["podSelector"][
|
||
|
|
"matchLabels"
|
||
|
|
]["apps.kubernetes.io/pod-index"] == str(ordinal)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"branch",
|
||
|
|
[
|
||
|
|
"../main",
|
||
|
|
"main",
|
||
|
|
"feature/../../main",
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_assignment_rejects_unreviewed_task_branch(monkeypatch, branch):
|
||
|
|
monkeypatch.setattr(
|
||
|
|
project,
|
||
|
|
"resolve_project",
|
||
|
|
lambda _board: (
|
||
|
|
"https://scm.bstein.dev/atlas/titan-iac.git", "main", Path("/unused")
|
||
|
|
),
|
||
|
|
)
|
||
|
|
task = SimpleNamespace(
|
||
|
|
id="t_deadbeef", workspace_path="", branch_name=branch,
|
||
|
|
repo_url="https://evil.example/atlas/other.git", base_branch="../main",
|
||
|
|
)
|
||
|
|
|
||
|
|
with pytest.raises(project.ProjectPolicyError):
|
||
|
|
project.resolve_assignment("titan-iac", task)
|
||
|
|
|
||
|
|
|
||
|
|
def test_registry_authority_ignores_unreviewed_task_repo_metadata(monkeypatch):
|
||
|
|
monkeypatch.setattr(
|
||
|
|
project,
|
||
|
|
"resolve_project",
|
||
|
|
lambda _board: (
|
||
|
|
"https://scm.bstein.dev/atlas/metis.git", "main", Path("/unused")
|
||
|
|
),
|
||
|
|
)
|
||
|
|
task = SimpleNamespace(
|
||
|
|
id="t_deadbeef", branch_name="review/t_deadbeef",
|
||
|
|
repo_url="https://evil.example/atlas/other.git", base_branch="../main",
|
||
|
|
)
|
||
|
|
assert project.resolve_assignment("metis", task)[:2] == (
|
||
|
|
"https://scm.bstein.dev/atlas/metis.git", "review/t_deadbeef"
|
||
|
|
)
|