238 lines
9.5 KiB
Python
238 lines
9.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from scripts import run_leo_agent_challenger_loop as runner
|
|
|
|
|
|
def test_remote_script_embeds_stateless_challenger_read_only_guard_and_bounded_context() -> None:
|
|
script = runner.build_remote_script("abc123", challenger_max_tokens=512)
|
|
|
|
assert "stateless_openrouter_agent" in script
|
|
assert "gatewayrunner_temp_profile_no_model_tools" in script
|
|
assert "blocked by isolated challenger-loop read-only guard" in script
|
|
context_source = runner._patched_db_context_source()
|
|
assert '"--limit",\n "4",' in context_source
|
|
assert '"--context-limit",\n "6",' in context_source
|
|
assert "CHALLENGER_MAX_TOKENS = 512" in script
|
|
assert "posted_to_telegram" in script
|
|
assert "db_fingerprint_unchanged" in script
|
|
assert '"temperature": 0.0' in script
|
|
assert "isolated_challenger_final_revision" in script
|
|
assert '"use_database_context": step < 3' in script
|
|
assert "LEO_CHALLENGER_CONTEXT_ONLY" in script
|
|
assert "Follow the challenger's requested labels exactly" in script
|
|
assert "challenger_generation_audit" in script
|
|
assert "evaluate_challenger_objection" in script
|
|
assert "build_challenger_retry_prompt" in script
|
|
assert "exhausted semantic regeneration attempts before Leo advance" in script
|
|
assert "challenger_semantic_gate_passed_before_leo_advance" in script
|
|
|
|
|
|
def test_remote_script_rejects_unbounded_tokens_and_unsafe_report_prefix() -> None:
|
|
with pytest.raises(ValueError, match="between 128 and 2048"):
|
|
runner.build_remote_script("abc123", challenger_max_tokens=4096)
|
|
|
|
with pytest.raises(ValueError, match="report_prefix"):
|
|
runner.build_remote_script("abc123", report_prefix="bad/path")
|
|
|
|
|
|
def test_remote_report_fetch_retries_timeout_without_deleting_receipt(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls = []
|
|
outcomes = iter(
|
|
(
|
|
runner.subprocess.TimeoutExpired("ssh", runner.REMOTE_REPORT_FETCH_TIMEOUT_SECONDS),
|
|
runner.subprocess.CompletedProcess(
|
|
args=["ssh"],
|
|
returncode=0,
|
|
stdout=json.dumps({"run_id": "abc123"}),
|
|
stderr="",
|
|
),
|
|
)
|
|
)
|
|
|
|
def fake_run(*args, **kwargs):
|
|
calls.append((args, kwargs))
|
|
outcome = next(outcomes)
|
|
if isinstance(outcome, Exception):
|
|
raise outcome
|
|
return outcome
|
|
|
|
monkeypatch.setattr(runner.subprocess, "run", fake_run)
|
|
|
|
report = runner.fetch_remote_report("abc123")
|
|
|
|
assert report == {"run_id": "abc123"}
|
|
assert len(calls) == 2
|
|
assert "p.unlink()" not in calls[0][1]["input"]
|
|
assert calls[0][1]["timeout"] == runner.REMOTE_REPORT_FETCH_TIMEOUT_SECONDS
|
|
|
|
|
|
def test_remote_report_unlink_retries_and_requires_absence_readback(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls = []
|
|
outcomes = iter(
|
|
(
|
|
runner.subprocess.TimeoutExpired("ssh", runner.REMOTE_REPORT_FETCH_TIMEOUT_SECONDS),
|
|
runner.subprocess.CompletedProcess(
|
|
args=["ssh"],
|
|
returncode=0,
|
|
stdout="absent\n",
|
|
stderr="",
|
|
),
|
|
)
|
|
)
|
|
|
|
def fake_run(*args, **kwargs):
|
|
calls.append((args, kwargs))
|
|
outcome = next(outcomes)
|
|
if isinstance(outcome, Exception):
|
|
raise outcome
|
|
return outcome
|
|
|
|
monkeypatch.setattr(runner.subprocess, "run", fake_run)
|
|
|
|
unlinked = runner.unlink_remote_report("abc123")
|
|
|
|
assert unlinked is True
|
|
assert len(calls) == 2
|
|
assert "p.unlink()" in calls[0][1]["input"]
|
|
assert "not p.exists()" in calls[0][1]["input"]
|
|
|
|
|
|
def test_run_remote_does_not_claim_cleanup_from_fetch_without_unlink_ack(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
def fake_run(args, **_kwargs):
|
|
if args[:2] == ["git", "rev-parse"]:
|
|
return runner.subprocess.CompletedProcess(args=args, returncode=0, stdout="a" * 40 + "\n", stderr="")
|
|
if args[:2] == ["git", "status"]:
|
|
return runner.subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
|
|
return runner.subprocess.CompletedProcess(
|
|
args=args,
|
|
returncode=0,
|
|
stdout=json.dumps({"run_id": "remote"}) + "\n",
|
|
stderr="",
|
|
)
|
|
|
|
monkeypatch.setattr(runner.subprocess, "run", fake_run)
|
|
monkeypatch.setattr(runner, "fetch_remote_report", lambda *_args, **_kwargs: {"run_id": "remote"})
|
|
monkeypatch.setattr(runner, "unlink_remote_report", lambda *_args, **_kwargs: False)
|
|
|
|
result = runner.run_remote()
|
|
|
|
assert result["execution_transport"]["remote_report_fetched_and_unlinked"] is False
|
|
|
|
|
|
def test_retention_projection_removes_runtime_paths_without_losing_hash_proof() -> None:
|
|
manifest = {
|
|
"schema": "livingip.leoBehaviorManifest.v1",
|
|
"generated_at_utc": "2026-07-15T00:00:00+00:00",
|
|
"behavior_sha256": "a" * 64,
|
|
"profile_root": "/home/teleo/.hermes/profiles/leoclean",
|
|
"hermes_root": "/home/teleo/.hermes/hermes-agent",
|
|
"deployment_root": "/opt/teleo-eval/workspaces/deploy-infra",
|
|
"model_runtime": {"status": "observed"},
|
|
"canonical_database": {"included": False},
|
|
"components": {
|
|
"conversation_sessions": {
|
|
"role": "session state",
|
|
"mutability": "dynamic",
|
|
"replayability": "hashable",
|
|
"content": {
|
|
"files": [{"path": "/home/teleo/private/session.json", "sha256": "b" * 64}],
|
|
"missing": [],
|
|
"symlinks": [],
|
|
"file_count": 1,
|
|
"total_bytes": 42,
|
|
"sha256": "c" * 64,
|
|
},
|
|
}
|
|
},
|
|
"hermes_runtime": {
|
|
"git_head": "d" * 40,
|
|
"git_state": {"worktree_clean": True},
|
|
"source_tree": {
|
|
"files": [{"path": "/home/teleo/private/runtime.py", "sha256": "e" * 64}],
|
|
"missing": [],
|
|
"file_count": 1,
|
|
"total_bytes": 84,
|
|
"sha256": "f" * 64,
|
|
},
|
|
},
|
|
"teleo_infrastructure_runtime": {
|
|
"git_head": "1" * 40,
|
|
"git_state": {"worktree_clean": True},
|
|
"source_tree": {
|
|
"files": [{"path": "/opt/teleo-eval/private/runner.py", "sha256": "2" * 64}],
|
|
"missing": [],
|
|
"file_count": 1,
|
|
"total_bytes": 126,
|
|
"sha256": "3" * 64,
|
|
},
|
|
},
|
|
}
|
|
role = {
|
|
"role": "leo",
|
|
"runtime_kind": "gatewayrunner_temp_profile_no_model_tools",
|
|
"profile_realpath": "/tmp/leo-agent-challenger-loop-test/leo-profile",
|
|
}
|
|
role["profile_realpath_sha256"] = runner._sha256_text(role["profile_realpath"])
|
|
role["contract_sha256"] = runner.protocol.compute_runtime_role_contract_sha256(role)
|
|
original_contract = role["contract_sha256"]
|
|
raw = {
|
|
"remote_report_path": "/tmp/leo-agent-challenger-loop-test.json",
|
|
"execution_transport": {
|
|
"recovery": {
|
|
"remote_path": "/tmp/leo-agent-challenger-loop-test.json",
|
|
"recovered_report_sha256": "5" * 64,
|
|
}
|
|
},
|
|
"isolation": {"roles": {"leo": role}},
|
|
"live_behavior_manifest_before": manifest,
|
|
"live_behavior_manifest_after": manifest,
|
|
}
|
|
|
|
retained = runner.sanitize_report_for_retention(raw)
|
|
encoded = json.dumps(retained, sort_keys=True)
|
|
|
|
assert "/home/" not in encoded
|
|
assert "/opt/" not in encoded
|
|
assert "/tmp/" not in encoded
|
|
assert raw["remote_report_path"].startswith("/tmp/")
|
|
assert retained["live_behavior_manifest_before"]["components"]["conversation_sessions"]["content"] == {
|
|
"file_count": 1,
|
|
"missing_count": 0,
|
|
"sha256": "c" * 64,
|
|
"symlink_count": 0,
|
|
"total_bytes": 42,
|
|
}
|
|
assert retained["live_behavior_manifest_before"]["retention_projection"]["removed_file_entries"] == 3
|
|
retained_role = retained["isolation"]["roles"]["leo"]
|
|
assert retained_role["profile_realpath"] is None
|
|
assert retained_role["profile_realpath_sha256"] == runner._sha256_text(
|
|
"/tmp/leo-agent-challenger-loop-test/leo-profile"
|
|
)
|
|
assert retained_role["contract_sha256"] == runner.protocol.compute_runtime_role_contract_sha256(retained_role)
|
|
assert retained["retention_projection"]["original_runtime_role_contract_sha256"]["leo"] == (original_contract)
|
|
assert retained["retention_projection"]["raw_remote_report_sha256"] == "5" * 64
|
|
assert retained["retention_projection"]["profile_realpath_hash_verified"] == {"leo": True}
|
|
assert runner.sanitize_report_for_retention(retained) == retained
|
|
retained["remote_stderr"] = "traceback from /home/teleo/private/runtime.py"
|
|
with pytest.raises(ValueError, match="forbidden absolute runtime path"):
|
|
runner.sanitize_report_for_retention(retained)
|
|
|
|
|
|
def test_retention_projection_fails_closed_on_mismatched_path_hash_or_unexpected_path() -> None:
|
|
mismatched_role = {
|
|
"role": "leo",
|
|
"profile_realpath": "/tmp/leo-profile",
|
|
"profile_realpath_sha256": "0" * 64,
|
|
}
|
|
mismatched_role["contract_sha256"] = runner.protocol.compute_runtime_role_contract_sha256(mismatched_role)
|
|
with pytest.raises(ValueError, match="profile realpath hash does not match"):
|
|
runner.sanitize_report_for_retention({"isolation": {"roles": {"leo": mismatched_role}}})
|
|
|
|
with pytest.raises(ValueError, match="forbidden absolute runtime path"):
|
|
runner.sanitize_report_for_retention({"remote_stderr": "traceback from /home/teleo/private/runtime.py"})
|