guardrail-space-verifiable-.../tests/test_contract.py

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)