59 lines
1.9 KiB
Python
59 lines
1.9 KiB
Python
import os
|
|
import sys
|
|
import pytest
|
|
|
|
# Ensure top-level package directory takes precedence over any on-src compatibility
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if ROOT not in sys.path:
|
|
sys.path.insert(0, ROOT)
|
|
|
|
from guardrail_space.contract import SafetyContract, Precondition, Budget
|
|
from guardrail_space.engine import evaluate_action
|
|
|
|
|
|
def test_precondition_veto():
|
|
contract = SafetyContract(
|
|
id="c1",
|
|
preconditions=[Precondition(key="state.battery", op=">=", value=10)],
|
|
budgets={"energy": Budget(name="energy", limit=100.0)},
|
|
)
|
|
|
|
state = {"battery": 5}
|
|
action = {"name": "drive", "costs": {"energy": 5}, "speed": 1.0}
|
|
|
|
verdict, payload = evaluate_action(contract, state, action)
|
|
assert verdict == "veto"
|
|
assert "precondition failed" in payload["reason"]
|
|
|
|
|
|
def test_budget_rewrite_to_safe_action():
|
|
contract = SafetyContract(
|
|
id="c2",
|
|
preconditions=[Precondition(key="state.battery", op=">=", value=1)],
|
|
budgets={"energy": Budget(name="energy", limit=2.0)},
|
|
)
|
|
|
|
state = {"battery": 50}
|
|
action = {"name": "drive", "costs": {"energy": 4.0}, "speed": 2.0}
|
|
|
|
verdict, payload = evaluate_action(contract, state, action)
|
|
# With rewrite, engine should accept a scaled-down action
|
|
assert verdict == "accept"
|
|
assert payload["speed"] == pytest.approx(1.0)
|
|
assert payload["costs"]["energy"] == pytest.approx(2.0)
|
|
|
|
|
|
def test_accept_action_and_charge_budget():
|
|
contract = SafetyContract(
|
|
id="c3",
|
|
preconditions=[Precondition(key="state.battery", op=">=", value=1)],
|
|
budgets={"energy": Budget(name="energy", limit=10.0)},
|
|
)
|
|
|
|
state = {"battery": 50}
|
|
action = {"name": "drive", "costs": {"energy": 3.0}, "speed": 1.0}
|
|
|
|
verdict, payload = evaluate_action(contract, state, action)
|
|
assert verdict == "accept"
|
|
assert contract.budgets["energy"].used == pytest.approx(3.0)
|