Merge feat-chv-muse-p9: inference/telemetry restricted channel
- executor.py: P9 _inference_gate inserted before P1 dispatcher
_run_with_vm; gate call is the first statement, so deny/ask
fail-closed before any resident resume or cold wake. P9's own
_run_with_vm (old-body variant) dropped: its body is byte-identical
to P1 _run_cold (verbatim move), kept once in _run_cold.
- sentinel.py: P9 superset version wins (P5 base + inference entries
POST /v1/responses, GET /v1/models + comments).
- inference_channel.py / telemetry.py / host.py: P9 as-is.
- Tests: P9 51 + closeup/gaps/optimize/cloud_hypervisor/fix 272 OK.
- mypy on changed files: P9's 5 cross-track errors gone (P5
capabilities.py now in tree); remaining 4 are 06c3e17 baseline
legacy (marker_tools.py:142, executor.py:1573/1578), untouched.
This commit is contained in:
commit
90c42f6e1d
13 files changed
+1455
-15
No files matched your search
@@ -85,6 +85,12 @@ def _write(path, content=b"x"):
|
||||
return path
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""feat-chv-muse-p9:假解析器(公网 IP),供推理出口预审用;
|
||||
实验室绝不联网,真实解析只在真机发生。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
class CloseupCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -113,7 +119,11 @@ class CloseupCase(unittest.TestCase):
|
||||
env_extra=None, **overrides):
|
||||
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
|
||||
"CHV_HOST_KEY_MODE": "keyscan",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
"CHV_SSH_FORWARD": "1",
|
||||
# feat-chv-muse-p9:推理出口预审的假 origin(公网
|
||||
# https,配假解析器;实验室不起真 VM、不联网)。
|
||||
"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
kwargs = dict(
|
||||
@@ -127,6 +137,7 @@ class CloseupCase(unittest.TestCase):
|
||||
sleeper=lambda s: None, env=env)
|
||||
kwargs.update(overrides)
|
||||
ex = CloudHypervisorExecutor(**kwargs)
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
|
||||
(e, t.get("id") if isinstance(t, dict) else t, d, k))
|
||||
return ex
|
||||
|
||||
@@ -73,6 +73,12 @@ def _write(path, content=b"x"):
|
||||
return path
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""feat-chv-muse-p9:假解析器(公网 IP),供推理出口预审用;
|
||||
实验室绝不联网,真实解析只在真机发生。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
class ChvFixCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -127,7 +133,11 @@ class ChvFixCase(unittest.TestCase):
|
||||
|
||||
def make_exec(self, runner=None, env_extra=None, **overrides):
|
||||
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
"CHV_SSH_FORWARD": "1",
|
||||
# feat-chv-muse-p9:推理出口预审的假 origin(公网
|
||||
# https,配假解析器;实验室不起真 VM、不联网)。
|
||||
"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
kwargs = dict(
|
||||
@@ -141,6 +151,7 @@ class ChvFixCase(unittest.TestCase):
|
||||
sleeper=lambda s: None, env=env)
|
||||
kwargs.update(overrides)
|
||||
ex = CloudHypervisorExecutor(**kwargs)
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
|
||||
(e, t.get("id") if isinstance(t, dict) else t, d, k))
|
||||
return ex
|
||||
@@ -305,8 +316,12 @@ class GatewayExtraEnvTest(ChvFixCase):
|
||||
task = {"id": "gw1", "project_id": "p1", "prompt": "做目标",
|
||||
"goal_mode": 1,
|
||||
"_gateway_provider": "sub2api",
|
||||
# feat-chv-muse-p9:网关 URL 须是合法推理 origin——
|
||||
# 这里用文档形状的本地中继(http + Docker 桥接 IP),
|
||||
# 预审走本地中继分支放行(字面 IP,不查 DNS)。
|
||||
"_gateway_env": {"SUB2API_API_KEY": "gwkey",
|
||||
"SUB2API_BASE_URL": "http://gw/"}}
|
||||
"SUB2API_BASE_URL":
|
||||
"http://172.17.0.1:28084"}}
|
||||
result, error = worker._run_attempt(task)
|
||||
self.assertIsNone(error)
|
||||
self.assertEqual(result["status"], "completed")
|
||||
|
||||
@@ -81,6 +81,12 @@ def _write(path, content=b"x"):
|
||||
return path
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""feat-chv-muse-p9:假解析器(公网 IP),供推理出口预审用;
|
||||
实验室绝不联网,真实解析只在真机发生。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
class ChvGapCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -129,7 +135,11 @@ class ChvGapCase(unittest.TestCase):
|
||||
# genisoimage,测的不是 host key 模式,故显式
|
||||
# 声明实验室路径,绝不把它写成生产默认。
|
||||
"CHV_HOST_KEY_MODE": "keyscan",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
"CHV_SSH_FORWARD": "1",
|
||||
# feat-chv-muse-p9:推理出口预审的假 origin(公网
|
||||
# https,配假解析器;实验室不起真 VM、不联网)。
|
||||
"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
kwargs = dict(
|
||||
@@ -143,6 +153,7 @@ class ChvGapCase(unittest.TestCase):
|
||||
sleeper=lambda s: None, env=env)
|
||||
kwargs.update(overrides)
|
||||
ex = CloudHypervisorExecutor(**kwargs)
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
|
||||
(e, t.get("id") if isinstance(t, dict) else t, d, k))
|
||||
return ex
|
||||
|
||||
@@ -0,0 +1,570 @@
|
||||
"""对齐 Muse P9:推理与遥测受限通道。
|
||||
|
||||
全部用标准库与 mock,不启动真实 VM、不联网、不碰 WSL/真机;
|
||||
解析器一律用假解析器(公网 IP / 私网 IP 两档)。
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
try:
|
||||
from cloud.chv.executor import CloudHypervisorExecutor
|
||||
from cloud.chv.inference_channel import (
|
||||
InferenceLimits,
|
||||
check_inference_call,
|
||||
inference_allow_hosts,
|
||||
inference_limits,
|
||||
resolve_inference_origin,
|
||||
)
|
||||
from cloud.chv.sentinel import SentinelPolicy
|
||||
from cloud.chv.telemetry import (
|
||||
build_task_telemetry,
|
||||
build_trajectory_event,
|
||||
emit_telemetry,
|
||||
sanitize_telemetry_event,
|
||||
sampled,
|
||||
set_training_enabled,
|
||||
telemetry_sample_rate,
|
||||
training_enabled,
|
||||
)
|
||||
from cloud.sandbox import ChvFailure
|
||||
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from cloud.chv.executor import CloudHypervisorExecutor
|
||||
from cloud.chv.inference_channel import (
|
||||
InferenceLimits,
|
||||
check_inference_call,
|
||||
inference_allow_hosts,
|
||||
inference_limits,
|
||||
resolve_inference_origin,
|
||||
)
|
||||
from cloud.chv.sentinel import SentinelPolicy
|
||||
from cloud.chv.telemetry import (
|
||||
build_task_telemetry,
|
||||
build_trajectory_event,
|
||||
emit_telemetry,
|
||||
sanitize_telemetry_event,
|
||||
sampled,
|
||||
set_training_enabled,
|
||||
telemetry_sample_rate,
|
||||
training_enabled,
|
||||
)
|
||||
from cloud.sandbox import ChvFailure
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""假解析器:公网 IP。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
def _private_resolver(host, port):
|
||||
"""假解析器:私网 IP(SSRF 场景)。"""
|
||||
return ["10.0.0.5"]
|
||||
|
||||
|
||||
def _load_host_module():
|
||||
"""以独立模块名加载仓库根的 host.py(只用标准库)。"""
|
||||
path = Path(__file__).resolve().parents[2] / "host.py"
|
||||
spec = importlib.util.spec_from_file_location("host_under_test", path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class OriginTest(unittest.TestCase):
|
||||
def test_valid_sub2api_origin(self):
|
||||
env = {"SUB2API_BASE_URL": "https://xgapi.lol/"}
|
||||
self.assertEqual(
|
||||
resolve_inference_origin("sub2api", env), "https://xgapi.lol")
|
||||
|
||||
def test_missing_origin_returns_empty(self):
|
||||
self.assertEqual(resolve_inference_origin("sub2api", {}), "")
|
||||
self.assertEqual(resolve_inference_origin(None, {}), "")
|
||||
|
||||
def test_http_origin_rejected(self):
|
||||
env = {"SUB2API_BASE_URL": "http://xgapi.lol"}
|
||||
with self.assertRaises(ValueError):
|
||||
resolve_inference_origin("sub2api", env)
|
||||
|
||||
def test_http_public_ip_rejected(self):
|
||||
env = {"SUB2API_BASE_URL": "http://8.8.8.8:28084"}
|
||||
with self.assertRaises(ValueError):
|
||||
resolve_inference_origin("sub2api", env)
|
||||
|
||||
def test_http_hostname_rejected(self):
|
||||
env = {"SUB2API_BASE_URL": "http://relay.local:28084"}
|
||||
with self.assertRaises(ValueError):
|
||||
resolve_inference_origin("sub2api", env)
|
||||
|
||||
def test_http_local_relay_allowed(self):
|
||||
# 文档形状:本地 sub2api 中继走 Docker 桥接 IP + http,
|
||||
# 端口保留;环回同款。
|
||||
env = {"SUB2API_BASE_URL": "http://172.17.0.1:28084/"}
|
||||
self.assertEqual(
|
||||
resolve_inference_origin("sub2api", env),
|
||||
"http://172.17.0.1:28084")
|
||||
env2 = {"SUB2API_BASE_URL": "http://127.0.0.1:28084"}
|
||||
self.assertEqual(
|
||||
resolve_inference_origin("sub2api", env2),
|
||||
"http://127.0.0.1:28084")
|
||||
|
||||
def test_userinfo_in_origin_rejected(self):
|
||||
env = {"SUB2API_BASE_URL": "https://user:pass@xgapi.lol"}
|
||||
with self.assertRaises(ValueError):
|
||||
resolve_inference_origin("sub2api", env)
|
||||
|
||||
def test_non_443_port_rejected(self):
|
||||
env = {"SUB2API_BASE_URL": "https://xgapi.lol:8443"}
|
||||
with self.assertRaises(ValueError):
|
||||
resolve_inference_origin("sub2api", env)
|
||||
|
||||
def test_unknown_provider_falls_back_to_default_origin(self):
|
||||
# 未知标签与 host.py `_server_command` 一致走默认网关;
|
||||
# 未配 CHV_INFERENCE_ORIGIN 时返回 ""(调用方按 ask 处理)。
|
||||
env = {"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
self.assertEqual(
|
||||
resolve_inference_origin("openai", env), "https://xgapi.lol")
|
||||
self.assertEqual(resolve_inference_origin("openai", {}), "")
|
||||
|
||||
def test_xcmapi_origin_from_explicit_env(self):
|
||||
env = {"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
self.assertEqual(
|
||||
resolve_inference_origin(None, env), "https://xgapi.lol")
|
||||
|
||||
|
||||
class AllowlistTest(unittest.TestCase):
|
||||
def test_explicit_allowlist_wins(self):
|
||||
env = {"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "a.example, B.example "}
|
||||
hosts = inference_allow_hosts("sub2api", env)
|
||||
self.assertEqual(hosts, frozenset({"a.example", "b.example"}))
|
||||
|
||||
def test_default_allowlist_is_origin_host(self):
|
||||
env = {"SUB2API_BASE_URL": "https://xgapi.lol"}
|
||||
self.assertEqual(inference_allow_hosts("sub2api", env),
|
||||
frozenset({"xgapi.lol"}))
|
||||
|
||||
def test_no_origin_means_empty_allowlist(self):
|
||||
self.assertEqual(inference_allow_hosts("sub2api", {}), frozenset())
|
||||
|
||||
|
||||
class LimitsTest(unittest.TestCase):
|
||||
def test_defaults(self):
|
||||
self.assertEqual(inference_limits({}),
|
||||
InferenceLimits(timeout_seconds=120,
|
||||
max_retries=1))
|
||||
|
||||
def test_env_override(self):
|
||||
env = {"CHV_INFERENCE_TIMEOUT": "60",
|
||||
"CHV_INFERENCE_MAX_RETRIES": "3"}
|
||||
self.assertEqual(inference_limits(env),
|
||||
InferenceLimits(timeout_seconds=60, max_retries=3))
|
||||
|
||||
def test_invalid_timeout_rejected(self):
|
||||
for bad in ("abc", "5", "601", "-1"):
|
||||
with self.assertRaises(ValueError):
|
||||
inference_limits({"CHV_INFERENCE_TIMEOUT": bad})
|
||||
|
||||
def test_invalid_retries_rejected(self):
|
||||
for bad in ("x", "-1", "6"):
|
||||
with self.assertRaises(ValueError):
|
||||
inference_limits({"CHV_INFERENCE_MAX_RETRIES": bad})
|
||||
|
||||
|
||||
class PreflightTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.env = {"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||||
|
||||
def test_allow_with_public_ip(self):
|
||||
check = check_inference_call(
|
||||
"sub2api", self.env, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "allow")
|
||||
self.assertIsNotNone(check.decision.capability)
|
||||
self.assertTrue(check.decision.capability.matches(check.request))
|
||||
self.assertEqual(check.request.host, "xgapi.lol")
|
||||
self.assertEqual(check.request.path, "/v1/responses")
|
||||
|
||||
def test_capability_single_use_300s(self):
|
||||
check = check_inference_call(
|
||||
"sub2api", self.env, resolve=_public_resolver, now=1000.0)
|
||||
cap = check.decision.capability
|
||||
self.assertTrue(cap.use(check.request, now=1000.0))
|
||||
self.assertFalse(cap.use(check.request, now=1000.0))
|
||||
self.assertFalse(cap.matches(check.request, now=2000.0))
|
||||
|
||||
def test_local_relay_allow_without_dns(self):
|
||||
# 本地中继是字面私网 IP:不查 DNS(解析器被调即错),
|
||||
# allowlist 默认即 origin 主机,直接 allow + 单次能力。
|
||||
def _must_not_resolve(host, port):
|
||||
raise AssertionError("local relay must not resolve")
|
||||
env = {"SUB2API_BASE_URL": "http://172.17.0.1:28084"}
|
||||
check = check_inference_call(
|
||||
"sub2api", env, resolve=_must_not_resolve)
|
||||
self.assertEqual(check.decision.verdict, "allow")
|
||||
self.assertIn("local_relay", check.decision.reasons)
|
||||
self.assertIsNotNone(check.decision.capability)
|
||||
self.assertEqual(check.request.host, "172.17.0.1")
|
||||
self.assertEqual(check.request.port, 28084)
|
||||
self.assertEqual(check.request.scheme, "http")
|
||||
|
||||
def test_local_relay_ask_when_not_allowlisted(self):
|
||||
env = {"SUB2API_BASE_URL": "http://172.17.0.1:28084",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||||
check = check_inference_call(
|
||||
"sub2api", env, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "ask")
|
||||
|
||||
def test_deny_on_private_ip(self):
|
||||
check = check_inference_call(
|
||||
"sub2api", self.env, resolve=_private_resolver)
|
||||
self.assertEqual(check.decision.verdict, "deny")
|
||||
self.assertIn("ssrf_private_ip", check.decision.reasons[0])
|
||||
|
||||
def test_ask_on_non_allowlisted_host(self):
|
||||
# 显式 allowlist 已配、origin 主机不在其中 → ask。
|
||||
# (未配 allowlist 时默认即 origin 主机自身,不会 ask——
|
||||
# 见 test_default_allowlist_is_origin_host。)
|
||||
env = {"SUB2API_BASE_URL": "https://unknown.example",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||||
check = check_inference_call(
|
||||
"sub2api", env, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "ask")
|
||||
|
||||
def test_ask_when_origin_not_configured(self):
|
||||
check = check_inference_call(
|
||||
None, {}, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "ask")
|
||||
self.assertIn("inference_origin_not_configured",
|
||||
check.decision.reasons)
|
||||
|
||||
def test_xcmapi_origin_allow(self):
|
||||
env = {"CHV_INFERENCE_ORIGIN": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||||
check = check_inference_call(
|
||||
None, env, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "allow")
|
||||
|
||||
def test_unknown_provider_falls_back_to_default_origin(self):
|
||||
# 未知标签不 deny:与 _server_command 一致走默认网关;
|
||||
# 未配 CHV_INFERENCE_ORIGIN 时仍是 ask(fail-closed)。
|
||||
env = {"CHV_INFERENCE_ORIGIN": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||||
check = check_inference_call(
|
||||
"prov-x", env, resolve=_public_resolver)
|
||||
self.assertEqual(check.decision.verdict, "allow")
|
||||
check2 = check_inference_call(
|
||||
"prov-x", {}, resolve=_public_resolver)
|
||||
self.assertEqual(check2.decision.verdict, "ask")
|
||||
|
||||
def test_deny_path_out_of_scope(self):
|
||||
policy = SentinelPolicy(
|
||||
allow_hosts={"xgapi.lol"},
|
||||
connector_paths={"inference": (("POST", "prefix",
|
||||
"/v1/responses"),)})
|
||||
from cloud.chv.sentinel import EgressRequest
|
||||
request = EgressRequest("inference", "GET", "https",
|
||||
"xgapi.lol", 443, "/v1/admin")
|
||||
decision = policy.evaluate(request, resolve=_public_resolver)
|
||||
self.assertEqual(decision.verdict, "deny")
|
||||
|
||||
def test_audit_record_has_no_values(self):
|
||||
check = check_inference_call(
|
||||
"sub2api", self.env, resolve=_public_resolver)
|
||||
record = check.decision.audit(check.request)
|
||||
blob = json.dumps(record)
|
||||
self.assertNotIn("xgapi.lol".replace("xgapi", "SECRET"), blob)
|
||||
self.assertEqual(record["host"], "xgapi.lol")
|
||||
self.assertEqual(record["verdict"], "allow")
|
||||
|
||||
|
||||
class FakeResult:
|
||||
def __init__(self, returncode=0, stdout="", stderr=""):
|
||||
self.returncode = returncode
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
|
||||
|
||||
def _write(path, content=b"x"):
|
||||
with open(path, "wb") as fh:
|
||||
fh.write(content)
|
||||
os.chmod(path, 0o755)
|
||||
return path
|
||||
|
||||
|
||||
class GateTest(unittest.TestCase):
|
||||
"""执行器预审:deny/ask 时不起 VM(wake 未被调用);allow 才继续。"""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.dir = self.tmp.name
|
||||
self.binary = _write(os.path.join(self.dir, "cloud-hypervisor"))
|
||||
self.ch_remote = _write(os.path.join(self.dir, "ch-remote"))
|
||||
self.kernel = _write(os.path.join(self.dir, "vmlinux"))
|
||||
self.rootfs = _write(os.path.join(self.dir, "disk.raw"), b"R" * 100)
|
||||
self.ssh_key = _write(os.path.join(self.dir, "id_chv"))
|
||||
self.kvm = _write(os.path.join(self.dir, "kvm"))
|
||||
self.cpuinfo = os.path.join(self.dir, "cpuinfo")
|
||||
with open(self.cpuinfo, "w") as fh:
|
||||
fh.write("flags : fpu vmx sse\n")
|
||||
self.state_dir = os.path.join(self.dir, "state")
|
||||
self.wake_calls = []
|
||||
self.audit = []
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def _make_exec(self, env_extra):
|
||||
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
|
||||
"CHV_HOST_KEY_MODE": "keyscan",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
env.update(env_extra)
|
||||
ex = CloudHypervisorExecutor(
|
||||
binary=self.binary, ch_remote=self.ch_remote,
|
||||
kernel=self.kernel, rootfs=self.rootfs,
|
||||
ssh_key=self.ssh_key, kvm_path=self.kvm,
|
||||
cpuinfo_path=self.cpuinfo, state_dir=self.state_dir,
|
||||
runner=lambda cmd, timeout=None: FakeResult(0, "", ""),
|
||||
launcher=lambda cmd: FakeResult(0, "", ""),
|
||||
api_client=lambda *a, **k: FakeResult(0, "", ""),
|
||||
sleeper=lambda s: None, env=env)
|
||||
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
|
||||
(e, t.get("id") if isinstance(t, dict) else t, d, k))
|
||||
return ex
|
||||
|
||||
def test_deny_blocks_wake(self):
|
||||
ex = self._make_exec(
|
||||
{"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"})
|
||||
ex.sentinel_resolver = _private_resolver
|
||||
ex.wake_vm = mock.Mock(side_effect=AssertionError("must not wake"))
|
||||
task = {"id": "t1", "prompt": "hi"}
|
||||
with self.assertRaises(ChvFailure) as ctx:
|
||||
ex.dispatch_task(task, model_provider="sub2api")
|
||||
self.assertEqual(ctx.exception.kind, "inference_egress_denied")
|
||||
ex.wake_vm.assert_not_called()
|
||||
kinds = [e[0] for e in self.audit]
|
||||
self.assertIn("inference_egress_deny", kinds)
|
||||
|
||||
def test_ask_blocks_wake_when_origin_missing(self):
|
||||
ex = self._make_exec({})
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.wake_vm = mock.Mock(side_effect=AssertionError("must not wake"))
|
||||
task = {"id": "t2", "prompt": "hi"}
|
||||
with self.assertRaises(ChvFailure) as ctx:
|
||||
ex.dispatch_task(task, model_provider=None)
|
||||
self.assertEqual(ctx.exception.kind, "inference_egress_denied")
|
||||
ex.wake_vm.assert_not_called()
|
||||
|
||||
def test_extra_env_origin_is_honored(self):
|
||||
# 网关配置随任务来(worker 的 extra_env),执行器
|
||||
# 进程 env 里没有 origin 也要放行。
|
||||
ex = self._make_exec({})
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.wake_vm = mock.Mock()
|
||||
task = {"id": "t3", "prompt": "hi"}
|
||||
with mock.patch.object(ex, "_run_guest",
|
||||
return_value={"status": "completed"}):
|
||||
with mock.patch.object(ex, "shutdown_vm"):
|
||||
ex.dispatch_task(
|
||||
task, model_provider="sub2api",
|
||||
extra_env={"SUB2API_BASE_URL": "https://xgapi.lol"})
|
||||
ex.wake_vm.assert_called_once()
|
||||
|
||||
def test_bad_origin_becomes_deny_not_value_error(self):
|
||||
ex = self._make_exec({})
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.wake_vm = mock.Mock(side_effect=AssertionError("must not wake"))
|
||||
task = {"id": "t4", "prompt": "hi"}
|
||||
with self.assertRaises(ChvFailure) as ctx:
|
||||
ex.dispatch_task(
|
||||
task, model_provider="sub2api",
|
||||
extra_env={"SUB2API_BASE_URL": "http://xgapi.lol"})
|
||||
self.assertEqual(ctx.exception.kind, "inference_egress_denied")
|
||||
ex.wake_vm.assert_not_called()
|
||||
kinds = [e[0] for e in self.audit]
|
||||
self.assertIn("inference_egress_deny", kinds)
|
||||
|
||||
|
||||
class TelemetryTest(unittest.TestCase):
|
||||
def test_allowlisted_fields_only(self):
|
||||
event = build_task_telemetry(
|
||||
task_id="t1", vm_id="vm1", provider="sub2api",
|
||||
model="gpt-6.1-terra", status="completed",
|
||||
started_at=1000.0, ended_at=1005.0, duration_ms=5000,
|
||||
tokens={"input_tokens": 10, "output_tokens": 5,
|
||||
"total_tokens": 15},
|
||||
turns=2, tool_calls=3, env={"CHV_TELEMETRY_SAMPLE_RATE": "1"})
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["tokens_in"], 10)
|
||||
self.assertEqual(event["tokens_total"], 15)
|
||||
self.assertEqual(event["turns"], 2)
|
||||
|
||||
def test_secret_and_text_keys_dropped(self):
|
||||
event = {"task_id": "t1", "vm_id": "v", "provider": "sub2api",
|
||||
"model": "m", "status": "completed",
|
||||
"started_at": 1.0, "ended_at": 2.0, "duration_ms": 1,
|
||||
"prompt": "do evil", "content": "secret file",
|
||||
"api_key": "sk-123", "user_email": "a@b.c",
|
||||
"extra_blob": {"nested": "data"}}
|
||||
clean = sanitize_telemetry_event(event)
|
||||
for bad in ("prompt", "content", "api_key", "user_email",
|
||||
"extra_blob"):
|
||||
self.assertNotIn(bad, clean)
|
||||
self.assertEqual(clean["task_id"], "t1")
|
||||
|
||||
def test_tokens_none_when_not_reported(self):
|
||||
event = build_task_telemetry(
|
||||
task_id="t1", vm_id="vm1", provider="sub2api", model=None,
|
||||
status="failed", started_at=1.0, ended_at=2.0, duration_ms=1,
|
||||
env={"CHV_TELEMETRY_SAMPLE_RATE": "1"})
|
||||
self.assertIsNone(event["tokens_in"])
|
||||
self.assertIsNone(event["tokens_out"])
|
||||
self.assertIsNone(event["tokens_total"])
|
||||
|
||||
def test_sampling_deterministic(self):
|
||||
self.assertEqual(sampled("task-abc", 1.0), True)
|
||||
self.assertEqual(sampled("task-abc", 0.0), False)
|
||||
self.assertEqual(sampled("task-abc", 0.5),
|
||||
sampled("task-abc", 0.5))
|
||||
event = build_task_telemetry(
|
||||
task_id="t1", vm_id="v", provider="sub2api", model=None,
|
||||
status="completed", started_at=1.0, ended_at=2.0,
|
||||
duration_ms=1, env={"CHV_TELEMETRY_SAMPLE_RATE": "0"})
|
||||
self.assertIsNone(event)
|
||||
|
||||
def test_invalid_sample_rate_rejected(self):
|
||||
with self.assertRaises(ValueError):
|
||||
telemetry_sample_rate({"CHV_TELEMETRY_SAMPLE_RATE": "2"})
|
||||
with self.assertRaises(ValueError):
|
||||
telemetry_sample_rate({"CHV_TELEMETRY_SAMPLE_RATE": "x"})
|
||||
|
||||
def test_emit_never_raises(self):
|
||||
self.assertFalse(emit_telemetry("/nonexistent/dir/xyz",
|
||||
{"task_id": "t1"}))
|
||||
self.assertFalse(emit_telemetry("", {"task_id": "t1"}))
|
||||
|
||||
def test_emit_writes_jsonl(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
event = {"task_id": "t9", "vm_id": "v", "status": "completed"}
|
||||
self.assertTrue(emit_telemetry(d, event))
|
||||
with open(os.path.join(d, "telemetry.jsonl")) as fh:
|
||||
row = json.loads(fh.read().strip())
|
||||
self.assertEqual(row["task_id"], "t9")
|
||||
self.assertNotIn("prompt", row)
|
||||
|
||||
|
||||
class TrainingOptTest(unittest.TestCase):
|
||||
def test_default_enabled(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
self.assertTrue(training_enabled(d))
|
||||
|
||||
def test_set_off_persists_and_blocks_trajectory(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
self.assertTrue(set_training_enabled(d, False))
|
||||
self.assertFalse(training_enabled(d))
|
||||
event = build_trajectory_event(
|
||||
"t1", 3, ["read", "write"], 100, d)
|
||||
self.assertIsNone(event)
|
||||
# 任务级遥测不受开关影响
|
||||
task_event = build_task_telemetry(
|
||||
"t1", "v", "sub2api", None, "completed",
|
||||
1.0, 2.0, 1, env={"CHV_TELEMETRY_SAMPLE_RATE": "1"})
|
||||
self.assertIsNotNone(task_event)
|
||||
|
||||
def test_trajectory_has_metadata_only(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
event = build_trajectory_event(
|
||||
"t1", 2, ["read", "exec"], 50, d)
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["tools"], ["read", "exec"])
|
||||
self.assertNotIn("prompt", event)
|
||||
self.assertNotIn("content", event)
|
||||
|
||||
def test_corrupt_state_fails_closed(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
with open(os.path.join(d, "training-opt.json"), "w") as fh:
|
||||
fh.write("{not json")
|
||||
self.assertFalse(training_enabled(d))
|
||||
|
||||
def test_non_bool_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
with self.assertRaises(TypeError):
|
||||
set_training_enabled(d, "off")
|
||||
|
||||
def test_switch_change_is_audited(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
set_training_enabled(d, False, actor="tester")
|
||||
audit_path = os.path.join(d, "training-opt-audit.jsonl")
|
||||
with open(audit_path) as fh:
|
||||
row = json.loads(fh.read().strip())
|
||||
self.assertEqual(row["event"], "training_opt_changed")
|
||||
self.assertFalse(row["enabled"])
|
||||
self.assertEqual(row["actor"], "tester")
|
||||
|
||||
|
||||
class HostPyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.host = _load_host_module()
|
||||
|
||||
def test_server_command_valid_origin(self):
|
||||
with mock.patch.dict(os.environ,
|
||||
{"SUB2API_BASE_URL": "https://xgapi.lol"}):
|
||||
cmd = self.host._server_command("codex", provider="sub2api")
|
||||
self.assertIn("app-server", cmd)
|
||||
self.assertTrue(any("xgapi.lol/v1" in c for c in cmd))
|
||||
|
||||
def test_server_command_http_origin_rejected(self):
|
||||
with mock.patch.dict(os.environ,
|
||||
{"SUB2API_BASE_URL": "http://xgapi.lol"}):
|
||||
with self.assertRaises(ValueError):
|
||||
self.host._server_command("codex", provider="sub2api")
|
||||
|
||||
def test_server_command_local_relay_allowed(self):
|
||||
# 文档形状的本地中继(http + 桥接 IP)必须通过第二道防线。
|
||||
with mock.patch.dict(os.environ,
|
||||
{"SUB2API_BASE_URL":
|
||||
"http://172.17.0.1:28084"}):
|
||||
cmd = self.host._server_command("codex", provider="sub2api")
|
||||
self.assertTrue(any("172.17.0.1:28084/v1" in c for c in cmd))
|
||||
|
||||
def test_server_command_allowlist_mismatch_rejected(self):
|
||||
with mock.patch.dict(os.environ,
|
||||
{"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ALLOW_HOSTS": "other.example"}):
|
||||
with self.assertRaises(ValueError):
|
||||
self.host._server_command("codex", provider="sub2api")
|
||||
|
||||
def test_request_timeout_default_and_env(self):
|
||||
self.assertEqual(
|
||||
self.host._inference_request_timeout(None), 120)
|
||||
self.assertEqual(
|
||||
self.host._inference_request_timeout(60), 60)
|
||||
with mock.patch.dict(os.environ,
|
||||
{"CHV_INFERENCE_TIMEOUT": "30"}):
|
||||
self.assertEqual(
|
||||
self.host._inference_request_timeout(None), 30)
|
||||
with mock.patch.dict(os.environ,
|
||||
{"CHV_INFERENCE_TIMEOUT": "5"}):
|
||||
with self.assertRaises(ValueError):
|
||||
self.host._inference_request_timeout(None)
|
||||
|
||||
def test_request_retries_default_and_env(self):
|
||||
self.assertEqual(self.host._inference_max_retries(), 1)
|
||||
with mock.patch.dict(os.environ,
|
||||
{"CHV_INFERENCE_MAX_RETRIES": "0"}):
|
||||
self.assertEqual(self.host._inference_max_retries(), 0)
|
||||
with mock.patch.dict(os.environ,
|
||||
{"CHV_INFERENCE_MAX_RETRIES": "9"}):
|
||||
with self.assertRaises(ValueError):
|
||||
self.host._inference_max_retries()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -86,6 +86,12 @@ def _write(path, content=b"x"):
|
||||
return path
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""feat-chv-muse-p9:假解析器(公网 IP),供推理出口预审用;
|
||||
实验室绝不联网,真实解析只在真机发生。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
class ChvOptCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -118,7 +124,11 @@ class ChvOptCase(unittest.TestCase):
|
||||
# genisoimage,测的不是 host key 模式,故显式
|
||||
# 声明实验室路径,绝不把它写成生产默认。
|
||||
"CHV_HOST_KEY_MODE": "keyscan",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
"CHV_SSH_FORWARD": "1",
|
||||
# feat-chv-muse-p9:推理出口预审的假 origin(公网
|
||||
# https,配假解析器;实验室不起真 VM、不联网)。
|
||||
"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
if runner is None:
|
||||
@@ -135,7 +145,9 @@ class ChvOptCase(unittest.TestCase):
|
||||
runner=runner, launcher=launcher, api_client=api_client,
|
||||
sleeper=lambda s: None, env=env)
|
||||
kwargs.update(overrides)
|
||||
return CloudHypervisorExecutor(**kwargs)
|
||||
ex = CloudHypervisorExecutor(**kwargs)
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
return ex
|
||||
|
||||
def _default_runner(self, cmd, timeout=None):
|
||||
self.calls.append(list(cmd))
|
||||
|
||||
@@ -73,6 +73,12 @@ def _write(path, content=b"x"):
|
||||
return path
|
||||
|
||||
|
||||
def _public_resolver(host, port):
|
||||
"""feat-chv-muse-p9:假解析器(公网 IP),供推理出口预审用;
|
||||
实验室绝不联网,真实解析只在真机发生。"""
|
||||
return ["93.184.216.34"]
|
||||
|
||||
|
||||
class ChvCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -106,7 +112,11 @@ class ChvCase(unittest.TestCase):
|
||||
# genisoimage,测的不是 host key 模式,故显式
|
||||
# 声明实验室路径,绝不把它写成生产默认。
|
||||
"CHV_HOST_KEY_MODE": "keyscan",
|
||||
"CHV_SSH_FORWARD": "1"}
|
||||
"CHV_SSH_FORWARD": "1",
|
||||
# feat-chv-muse-p9:推理出口预审的假 origin(公网
|
||||
# https,配假解析器;实验室不起真 VM、不联网)。
|
||||
"SUB2API_BASE_URL": "https://xgapi.lol",
|
||||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol"}
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
if runner is None:
|
||||
@@ -124,6 +134,7 @@ class ChvCase(unittest.TestCase):
|
||||
sleeper=lambda s: None, env=env)
|
||||
kwargs.update(overrides)
|
||||
ex = CloudHypervisorExecutor(**kwargs)
|
||||
ex.sentinel_resolver = _public_resolver
|
||||
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
|
||||
(e, t.get("id") if isinstance(t, dict) else t, d, k))
|
||||
return ex
|
||||
|
||||
Reference in new issue
Block a user