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)