Spaces:
Paused
Paused
File size: 7,684 Bytes
b5df635 963e554 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | """Phase 3 tests: tool registry permissions, audit completeness, allowlist."""
from __future__ import annotations
import json
import time
import pytest
from pydantic import BaseModel
from sensorflow.retro import store as retro_store
from sensorflow.retro.tools.registry import ToolPermissionError, ToolRegistry
class _EchoIn(BaseModel):
msg: str
class _EchoOut(BaseModel):
echoed: str
def test_read_only_is_the_default(registry):
specs = {s.name: s for s in registry.specs()}
assert specs["log_reader"].read_only is True
assert specs["safety_standard_rag"].read_only is True
assert specs["metric_calculator"].read_only is True
assert specs["historical_failure_search"].read_only is True
assert specs["distribution_analysis"].read_only is True
# exactly one write tool exists
write_tools = [s for s in specs.values() if not s.read_only]
assert [t.name for t in write_tools] == ["create_evaluation_case"]
def test_write_tool_requires_explicit_authorization(registry, retro_root):
args = {"title": "Rain-night pedestrian case",
"description": "derived from retrospective EVAL-2026-0802-MP01",
"scenario_tags": ["rain", "night", "pedestrian"],
"source_evaluation_id": "EVAL-2026-0802-MP01"}
with pytest.raises(ToolPermissionError):
registry.call("create_evaluation_case", args)
denied = [r for r in registry.audit_log if r.status == "denied"]
assert denied and denied[0].tool == "create_evaluation_case"
res = registry.call("create_evaluation_case", args, policy_authorization=True)
assert res.ok
case_path = res.result["path"]
assert json.loads(open(case_path).read())["title"] == args["title"]
authorized = [r for r in registry.audit_log
if r.tool == "create_evaluation_case" and r.status == "ok"]
assert authorized and authorized[0].authorized_write is True
def test_audit_completeness(registry, retro_root):
registry.call("safety_standard_rag", {"query": "phantom braking", "k": 2})
with pytest.raises(Exception):
registry.call("metric_calculator", {"operation": "nope", "params": {}})
with pytest.raises(KeyError):
registry.call("not_a_tool", {})
# every call — ok, error, unknown — is audited with args + timestamp
assert len(registry.audit_log) == 3
for rec in registry.audit_log:
assert rec.timestamp and rec.args is not None and rec.call_id
ok = registry.audit_log[0]
assert ok.status == "ok" and len(ok.result_hash) == 64
assert registry.audit_log[1].status == "error"
assert registry.audit_log[2].status == "error"
# persisted to the isolated runs root as jsonl
persisted = retro_store.read_audit("test-analysis")
assert len(persisted) == 3
def test_result_hash_is_deterministic(registry, retro_root):
r1 = registry.call("metric_calculator", {
"operation": "stopping_distance", "params": {"velocity_mps": 10.0}})
r2 = registry.call("metric_calculator", {
"operation": "stopping_distance", "params": {"velocity_mps": 10.0}})
hashes = [rec.result_hash for rec in registry.audit_log if rec.status == "ok"]
assert hashes[0] == hashes[1]
assert r1.result == r2.result
def test_path_allowlist_enforced(registry, tmp_path):
secret = tmp_path / "secret.json"
secret.write_text("{}")
with pytest.raises(PermissionError, match="allowlist"):
registry.call("log_reader", {"path": str(secret)})
with pytest.raises(PermissionError):
registry.call("log_reader", {"fixture_id": "../../../etc/passwd"})
# uploads dir IS allowed
up = retro_store.uploads_dir() / "ok.json"
up.write_text(json.dumps({"evaluation_id": "X", "ground_truth": {"class": "car"}}))
res = registry.call("log_reader", {"path": str(up)})
assert res.ok and res.result["log"]["evaluation_id"] == "X"
def test_timeout_enforced(retro_root):
reg = ToolRegistry(analysis_id="timeout-test")
def slow(_inp: _EchoIn) -> _EchoOut:
time.sleep(1.0)
return _EchoOut(echoed="late")
reg.register("slow_echo", slow, _EchoIn, _EchoOut, "slow", timeout_s=0.1)
with pytest.raises(TimeoutError):
reg.call("slow_echo", {"msg": "hi"})
assert reg.audit_log[-1].status == "timeout"
def test_error_behavior_return_error(retro_root):
reg = ToolRegistry(analysis_id="errbehavior-test")
def boom(_inp: _EchoIn) -> _EchoOut:
raise RuntimeError("kaput")
reg.register("boom", boom, _EchoIn, _EchoOut, "explodes",
error_behavior="return_error")
res = reg.call("boom", {"msg": "x"})
assert not res.ok and "kaput" in res.error
assert reg.audit_log[-1].status == "error"
def test_tool_schemas_declared(registry):
for spec in registry.specs():
assert spec.input_schema.get("properties") is not None
assert spec.output_schema.get("properties") is not None
assert spec.timeout_s > 0
assert spec.description
# ---------------------------------------------- megaeval delegation (real data)
@pytest.fixture(scope="module")
def mega_run(tmp_path_factory):
"""A real, published megaeval run in an isolated store (public API only)."""
from sensorflow.megaeval import population as pop_mod
from sensorflow.megaeval.runs import get_mega_store, reset_mega_store
root = tmp_path_factory.mktemp("retro-megaeval")
pop_mod.set_mega_root(str(root))
reset_mega_store()
# 20k objects: large enough that skewed cohorts clear distribution_shift's
# default min_eval_count=300 / rel_threshold=0.35 (8k leaves zero eligible)
meta = pop_mod.generate_population("retro-delegation-pop",
num_objects=20_000, seed=23)
store = get_mega_store()
run = store.create_run(population_id=meta["population_id"],
model_version="retro-delegation-model",
worker_delay_s=0.0)
store.execute_sync(run)
assert run.status == "published", run.error
yield run
pop_mod.set_mega_root("runs/megaeval")
reset_mega_store()
def test_distribution_analysis_delegates_to_megaeval(registry, mega_run):
"""The SUCCESS path: import + call chain into megaeval actually works."""
res = registry.call("distribution_analysis", {"run_id": mega_run.run_id})
assert res.ok
out = res.result
# delegation succeeded — not the graceful-degradation branch
assert out["source"] == "sensorflow.megaeval.analysis.distribution_shift"
assert out["shift"]["run_id"] == mega_run.run_id
assert "train mix vs eval mix" in out["shift"]["method"]
# the synthetic training mix is deliberately skewed (night/rain cohorts),
# so a real run surfaces genuine shifted cohorts
shifts = out["shift"]["shifts"]
assert len(shifts) > 0
for s in shifts:
assert {"cohort", "train_share", "eval_share",
"relative_change"} <= set(s)
assert any("cohort" in f for f in out["findings"])
# the call is audited like any other tool call
audited = [r for r in registry.audit_log
if r.tool == "distribution_analysis" and r.status == "ok"]
assert audited and len(audited[0].result_hash) == 64
def test_distribution_analysis_degrades_for_unknown_run(registry, mega_run):
"""Graceful degradation is reserved for genuinely missing data."""
res = registry.call("distribution_analysis", {"run_id": "eval-nonexistent"})
assert res.ok
assert res.result["source"] == "megaeval-delegation-failed"
assert res.result["shift"] is None
assert "no distribution claim is made" in res.result["findings"][0]
|